Elements with fixed position moving when body overflow is hidden - javascript

I want to open a modal layer which overtakes the body scroll. To accomplish that, when the layer is shown I'm setting the body overflow to hidden and the overflow to scroll on the modal layer. Visually, one scrollbar replaces the other.
In the background I have a top bar with fixed position and 100% wide. What happens is when the body overflow is set to hidden, the 100% width div (top bar) takes the scrollbar space and its elements move to the right.
How can I prevent those elements from moving?
I tried to calculate (javascript) the width of the scrollbar and when setting the body overflow: hidden, give a margin-right: "scrollbar width" to the top bar. That didn't work.
Also tried a dummy div at the right end of the top bar with overflow set to scroll and force it to display a scroll bar when the layer is opened. The idea was to take the space of the missing scrollbar with another scrollbar, only on the top container. That almost worked but created a 1 or 2px flickering. Not good enough.
jsFiddle here with the basic problem
var body = $('body'),
main = $('.main'),
open_modal = $('.open-modal'),
close_modal = $('.close-modal'),
modal_container = $('.modal-container'),
toggleModal = function() {
body.toggleClass('body-locked');
modal_container.toggleClass('dp-block');
};
open_modal.on('click', toggleModal);
close_modal.on('click', toggleModal);

Basically...
When the modal is opened, set the menu width to it's current width and set a window.onresize event handler which will resize the menu to the body's width.
When the modal is closed, remove the fixed width and the window.onresize handler and return the menu to it's initial state.
In the spirit of less === more I've taken the liberty of simplifying your code as much as I can.
var body = $('body');
var menu = $('#topBarFixed');
function toggleModal() {
menu.css('width', body.hasClass('locked') ? '' : menu.width());
window.onresize = body.hasClass('locked') ? '' : function () {
menu.css('width', body.width());
}
body.toggleClass('locked');
}
body.on('click', '.open-modal, .close-modal', toggleModal);
body {
padding-top: 40px;
height: 1000px;
background: lightblue;
}
body.locked {
height: 100%;
overflow: hidden;
}
.modal-container {
display: none;
overflow-y: scroll;
position: fixed;
top: 0; right: 0;
height: 100%; width: 100%;
background-color: rgba(255, 255, 255, 0.3);
z-index: 400;
}
body.locked .modal-container {
display: block !important;
}
.modal {
height: 600px;
width: 200px;
margin: 50px auto;
background: indianred;
}
#topBarFixed {
width: 100%;
background-color: lightgray;
position: fixed;
top: 0;
left: 0;
text-align:center;
display: inline-block;
z-index: 200;
}
.topBarContent {
display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
}
.inner1 {
width:30px;
line-height: 40px;
}
.open-modal {
position: relative;
top: 400px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="topBarFixed">
<div class="topBarContent">
<div id="inner" class="inner1">div</div>
<div id="inner" class="inner1">div</div>
<div id="inner" class="inner1">div</div>
<div id="inner" class="inner1">div</div>
<div id="inner" class="inner1">div</div>
</div>
</div>
<p>Scroll down to open layer</p>
<button class="open-modal">Open layer</button>
<div class="modal-container">
<div class="modal">
<button class="close-modal">Close layer</button>
</div>
</div>

Your problem here is that topBarFixed has a 100% width. If this width was fixed you would not have this problem. The following has been tested on Chrome and Firefox:
Add this line to your toggleModal function's first line:
$(".topBarFixed").width($(".topBarFixed").width());
That will set the width to the actual width (in pixels) of the bar at that point. Then when you close the layer, set it back to 100%.
close_modal.on('click', function() { toggleModal(); $(".topBarFixed").width("100%"); });
The entire code looks like:
var body = $('body'),
main = $('.main'),
open_modal = $('.open-modal'),
close_modal = $('.close-modal'),
modal_container = $('.modal-container'),
toggleModal = function() {
$(".topBarFixed").width($(".topBarFixed").width());
body.toggleClass('body-locked');
modal_container.toggleClass('dp-block');
};
open_modal.on('click', toggleModal);
close_modal.on('click', function() { toggleModal(); $(".topBarFixed").width("100%"); });
And here is the jsFiddle: http://jsfiddle.net/wmk05t0b/5/
Edit
Optionally, you could just come up with a fixed width, and that will do the trick:
.topBarFixed
{
width:715px; /*changed from 100%*/
height: 40px;
background-color: lightgray;
position: fixed;
top: 0;
left: 0;
text-align:center;
display: inline-block;
z-index: 200;
}

Some errors in your code: id is only one. Use classes if you want to apply the same style to more elements.
<div class="topBarContent">
<div class="inner1">div</div>
<div class="inner1">div</div>
<div class="inner1">div</div>
<div class="inner1">div</div>
<div class="inner1">div</div>
</div>
Anyways, that's not what caused your problem. First of all, your body's overflow should be enough: don't add an overflowY to your .modal-container unless you want to prevent the background page from scrolling while modal is open. Second, fix the modal itself, and center it using the centered CSS trick (left:50%, margin-left:-half-of-your-width).
CSS:
.body-locked {
overflow:scroll;
}
.modal-container {
overflow:hidden;
position:fixed;
display: none;
top: 0; right: 0;
height: 100%; width: 100%;
background-color: rgba(255, 255, 255, 0.3);
z-index: 400;
}
.modal {
position: fixed;
height: 600px;
width: 200px;
margin: 50px auto 50px -100px;
background: indianred;
left:50%;
}
/*Reset your body, you never know*/
body {
margin:0;
padding:0
}
Hope it helps.

Related

How to make the scroll event bubbling on a fixed positionned element

I have HTML elements with a position: fixed within an second element with an overflow-y :auto
When the mouse is over the fixed element the scroll doesn't bubble to it's parent. When moving away, the scroll occurs.
I've reduced the situation to the code below
main {
/* I am the element that scrolls */
height: 200px;
display: flex;
flex-flow: row nowrap;
overflow-y: auto;
}
main section {
flex: 1 1 800px;
max-width: 800px;
}
main nav {
flex: 1 1 auto;
min-width: 100px;
}
main nav a {
/* I am the element that prevent scroll on when hovering */
position: fixed;
}
<main>
<aside>
<nav>
left
</nav>
</aside>
<section>
<p>Could be a long text...</p>
</section>
<aside>
<nav>
right
</nav>
</aside>
</main>
You can test it at JSBin by trying to scroll over the pink element or somewhere else.
Is there a way in CSS or even in JavaScript ?
Seems Straight forward, position:fixed and/or position:absolute as you may now takes the element out of the document flow as if it doesn't exists, like it's somewhat just there for visual effects.
position:absolute still can bubble the scroll events of it's relative positioned ancestor as we know.
.parent {
height: 100px;
width: 100px;
border: 1px solid;
overflow-y: auto;
/* Take this out to see the effect */
position: relative;
}
p {
height: 5000px;
/* Mimic overflow */
}
.kid {
height: 20px;
width: 20px;
border: 1px solid;
position: absolute;
}
<div class="parent">
<div class="kid"></div>
<p></p>
</div>
However position:fixed is special, because it's relative to the browser window (viewport), it'll bubble the scroll event of the browser.
body {
/* Mimic body has scrollbars */
height: 5000px;
}
.parent {
height: 100px;
width: 100px;
border: 1px solid;
overflow-y: auto;
/* Take this out to see the effect */
position: relative;
}
p {
/* Mimic overflow */
height: 5000px;
}
.kid {
height: 20px;
width: 20px;
border: 1px solid;
position: fixed;
}
<div class="parent">
<div class="kid"></div>
<p></p>
</div>
Basically you can't make a fixed position element bubble up it's parent scroll event.
Using JavaScript you can still handle the wheel event and trigger it to the parent element:
const fixedElts = document.querySelectorAll('nav a');
const parentElt = document.querySelectorAll('main');
const bubbleFixedScroll = e => {
if(e.deltaMode){
parentElt.scrollTop += e.deltaY;
}
};
fixedElts.forEach( fixedElt => {
fixedElt.addEventListener('wheel', bubbleFixedScroll)
});
Of course the delta value can be adapted for a smother scrolling. Also some throttling can be added to prevent to much handlers to be executed.

hidden footer gradually appears on scroll

I am trying to make this effect on a project
http://www.cera-groupecera.com/en/
like this page the footer is hidden and appears as you scroll.
The page is wrapped in a page-content element and the footer is fixed to the bottom z-indexed 0
what happens is as you reach the end of the window the page -content margin rises as you scroll.
I can't really find a way to do it with j query
Here is an example,you just position it on the bottom of the page or in any other place you want it to stay,to hide it you can use z-index=-1;
//<br><br><br><br><br><br><br><br><br>
<h2><code>fixed</code></h2>
<div class="fixed"><div class="expand"></div></div>
<br><br><br><br><br><br><br><br>
CSS
#import "compass/css3";
h2 {
text-align: center;
margin-top: 48px;
}
div {
height: 200px;
width: 50%;
max-width: 600px;
margin: 32px auto;
overflow-x: hidden;
// overflow-y: scroll;
}
.fixed {
background: url('http://lorempixel.com/600/200/animals');
background-attachment: fixed;
overflow: hidden;
}
.expand {
height: 400px;
width: 100%;
}
https://jsfiddle.net/cu01m218/
I did one example for you.
$(function(){
calcFooter();
function calcFooter () {
var footer = $('.footer').height();
var mainContent = $('.main-content');
mainContent.css('margin-bottom', footer);
}
$(window).resize(calcFooter);
});
body {
margin: 0;
}
.main-content {
position: relative;
z-index: 100;
height: 100vh;
background-color: grey;
}
.footer {
position: fixed;
bottom: 0;
left: 0;
z-index: 10;
height: 200px;
width: 100%;
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="wrapper">
<div class="main-content" style="margin-bottom: 200px;">
<h1>This is main content.</h1>
<p>Scroll down to reveal footer!</p>
</div>
<div class="footer">
<p>This footer.</p>
</div>
</div>
Let's say that this element which appears at the bottom is footer tag.
In this case it will be something like this:
html:
<footer></footer>
in your css:
footer {
position: fixed;
bottom: 0;
display: none;
}
Then you have to add a class which will make footer appear
.active {
display: block;
}
and your jquery will be something like this:
$(window).on('scroll', function () {
if ($(this).scrollTop() > 50) {
if (!$(footer).hasClass('active')) {
$(footer).addClass('active');
}
} else {
if ($(footer).hasClass('active')) {
$(footer).removeClass('active');
}
}
});

javascript dynamically create overlay background

I have a function that when clicked takes the div container and centers it on screen, enlarged with fixed position and z-index of 2. I would like to dynamically create an element to sit z-index of 1 underneath the div with a black background, partially transparent, that hides the main content. How do I create and place this element into the page and delete it afterwards?
This demo has on overlay that's loaded on window onload event (#overlay).
There's a password input in #overlay (#pass is "off")
#overlay will use classList to change it's class to.off once the password is entered, thereby rendering #overlay non-existent (display: none).
var ov = document.getElementById('overlay');
var ps = document.getElementById('pass');
ps.addEventListener('change', function(e) {
if (pass.value === "off" && ov.classList.contains('on')) {
ov.classList.add('off');
ov.classList.remove('on');
} else {
alert('password is incorrect');
}
}, false);
function init() {
ov.classList.add('on');
}
window.onload = init;
#overlay {
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 1;
width: 100%;
height: 100%;
pointer-events: none;
}
.content {
border: 3px solid red;
width: 400px;
height: 200px;
margin: 50px auto;
padding: 10px;
}
p {
font: 600 16px/1.428'Arial' margin: 0 0 15px 10px;
}
#pass {
pointer-events: auto;
width: 100px;
line-height: 20px;
text-align: center;
margin: 25% auto 0;
display: block;
}
.off {
display: none;
}
.on {
display: block;
}
<div id="overlay" class="off">
<input id="pass" name="pass" type="password" placeholder="Enter Password">
</div>
<div class="content">
<p>xxxxxxxxxxxxxxxxxxxxxxxxxx</p>
<p>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</p>
<p>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</p>
</div>
I have a function that when clicked takes the div container and centers it on screen
Since you are doing this you have more control over it, All you have to so it is add this part of html along with this div of yours. (your div and this new div must be wrapped inside a parent div)
HTML
<div class="overlay-parent">
<div class="overlay"></div>
/* your div */
</div>
CSS
.overlay-parent{
width:100%;
height:100%;
overflow:hidden;
}
.overlay{
position:absolute;
top:0;
bottom:0;
width:100%;
background-color: black;
opacity:0.3;
}
This should be the structure when you are centering the div to center of the screen. And appending this structure to the body tag will get you the desired result

Can I use `overflow: scroll` on a div with variable height?

Is it possible to use overflow: scroll on a div that has height set to auto?
I have a div with an unordered list inside of it. The amount of items in the list is variable so there is no way I can use a fixed height. The div that contains the unordered list is where the scrollbars need to be, here is my code:
#page {
height: auto; /* default */
overflow-y: scroll;
overflow-x: hidden;
}
As stated, the unordered list is contained within the #page div. The height of the page is assigned by the unordered list's value. Is there a way to make overflow: scroll work on a div with variable height like this or must I use JavaScript to do this?
Thanks
One way of approaching this design...
Suppose that you have the following HTML:
<div class="main">
<div class="inner">
<ul>
<li>Some list items...</li>
...
</ul>
</div>
</div>
The .main block is fitted to the page, for example, by absolute positioning:
.main {
border: 2px dashed blue;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
The .inner block holds the navigation list that can cause scrolling:
.inner {
border: 2px dotted red;
position: absolute;
top: 10px;
bottom: 10px;
left: 10px;
right: 10px;
overflow-y: scroll;
}
In this example, I constrain the height of the .inner block to fit within .main,
and set overflow-y: scroll, which creates a scroll bar contained within the edges
of the container block.
You may have to adapt this to your mobile platform, but the concept should still apply.
Demo fiddle: http://jsfiddle.net/audetwebdesign/ac4xT/
Simply put, if it has variable height (auto), it will never have overflow in the y axis (vertically), because the div will always grow to fit its contents.
overflow: scroll will force it to present a scrollbar, but it will always be disabled, because the contents will never extend beyond the displayed pane.
If you want vertical scrolling, you have to define a height, either in px, %, or em.
If you do height: 100%, the div will fill the height of the page, and scroll content that extends beyond the window's viewport height.
If you have a header area, try something like this:
body {
margin: 0;
}
#header {
position: absolute;
width: 100%;
height: 40%;
background-color: #ccc;
}
#body {
position: absolute;
top: 40%;
width: 100%;
height: 60%;
overflow-y: auto;
background-color: #eee;
}
<body>
<div id="header">
<p>Header</p>
</div>
<div id="body">
<p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p>
<p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p>
</div>
</body>
For a fixed-height header (per the comments), use absolute positioning with a top and botom value to position the scrollable div below it:
body {
margin: 0;
}
#header {
position: absolute;
width: 100%;
height: 60px;
background-color: #ccc;
}
#body {
position: absolute;
top: 60px;
bottom: 0px;
width: 100%;
overflow-y: auto;
background-color: #eee;
}
<body>
<div id="header">
<p>Header</p>
</div>
<div id="body">
<p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p>
<p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p><p>Body</p>
</div>
</body>
Why not use max-height on the div?
max-height sets the maximum height to which an element can expand. I suppose what you want is the div to never go out of the screen. So you can set a max-height and then overflow: auto;

Put overlay on document with transparent window

I would like to do something with my document which is quite unique (haven't seen it before) and thus maybe not even possible.
What I would like is to have a div which will overlay everything in the document, maybe give it background black so that nothing is visible. Second I would like to have a small squire window in the overlay which doesn't share the black background, in fact it is somewhat transparent and therefore it would be possible to 'peek' trough that window to see document content. But only the content where this window is. It would be kinda like those "zoom" plugins in which only a small portion is being zoomed, but in this case it would show specific content. Any idea how to create such a thing?
An example of what you can do is the following (it may not be the best but it works)
HTML
<div id='peakview'></div> <!-- This div is your view window -->
<div id='out'>
<div class='overlay'></div>
<div class='overlay'></div>
<div class='overlay'></div>
<div class='overlay'></div>
</div>
The <div> inside of #out will re-size accordingly to the position of #peakview creating the illusion of a full overlay. This can be done with simple css and some calculus.
Mainly what you need is the position of the element on screen.
var h = $(this).offset().top;
var l = $(this).offset().left;
var r = ($(window).width() - ($(this).offset().left + $(this).outerWidth()));
//right offset
var b = ($(window).height() - ($(this).offset().top + $(this).outerWidth()));
//bottom offset
In my example I used .draggable() from jQuery UI to move the div around. And while dragging the 4 divs shown above are adjusting their height and width to fill up the space between #peakview and document border.
An example for the first div
$('.overlay:eq(0)').css({
top: 0,
left: 0,
width: '100%',
height: h //the height is always changing depending on the #peakview .offset().top
});
In this fiddle you will see how the filling divs behave
Another ruff start:
http://jsfiddle.net/XDrSA/
This require some extra work, but it may suit your needs.
HTML:
<div id="yourContent" style="width: 300px; margin:100px auto;">
<input type="button" id="zoom" value="Click to zoom"/>
</div>
<div id="zoomer">
<div id="window">This is your "window"</div>
<div id="overlay_top"></div>
<div id="overlay_left"></div>
<div id="overlay_right"></div>
<div id="overlay_bottom"></div>
</div>
CSS:
body {
margin:0;
padding:0;
}
#zoomer {
height: 100%;
width: 100%;
position: absolute;
top: 0;
display: none;
}
#overlay_top {
height: 20%;
width: 100%;
background-color: black;
position: absolute;
top: 0
}
#overlay_right {
height: 100%;
width: 20%;
background-color: black;
position: absolute;
right: 0;
}
#overlay_left {
height: 100%;
width: 20%;
background-color: black;
position: absolute;
left: 0;
}
#overlay_bottom {
height: 20%;
width: 100%;
background-color: black;
position: absolute;
bottom: 0;
}
#window {
margin: 0 auto;
height: 100%;
width: 80%;
position: absolute;
background-color: rgba(0,0,0,.5);
}
And a piece of javascript:
$('#zoom').click(function() {
$('#zoomer').fadeIn();
});
You may need to stumble with the positioning, and the window will be a fixed size one. Not draggable though.

Categories