Reputation: 6796
I have a layout which has a maximum width and which is centred horizontally when the screen width is greater than that maximum. The layout includes a fixed header & menu; when the screen width is less than the max, the menu's left
position is 0
and, when the screen width exceeds the max, the menu's left
position needs to be flush with the left edge of the rest of the layout.
Here's how it should look:
*{box-sizing:border-box;margin:0;padding:0;}
header{
align-items:center;
background:#eee;
border-bottom:1px solid #999;
display:flex;
height:100px;
left:0;
justify-content:center;
padding:0 10px;
position:fixed;
right:0;
top:0;
}
h1{
height:60px;
position:relative;
width:calc(100% - 40px);
max-width:740px;
z-index:2;
}
img{
height:100%;
width:auto;
}
nav{
background:#eee;
border-right:1px solid #999;
bottom:0;
left:0;
position:fixed;
top:0;
width:300px;
z-index:1;
}
@media (min-width:801px){
nav{
border-left:1px solid #999;
left:calc((100% - 800px) / 2)
}
}
nav::before{background:#ecc;bottom:0;content:"";left:0;position:absolute;top:0;width:10px;}
nav::after{background:#999;content:"";height:1px;left:0;position:absolute;right:0;top:99px;}
main{background:#ddf;border-left:1px solid #99c;border-right:1px solid #99c;height:100vh;min-height:100%;margin:0 auto;width:100%;max-width:800px;}
<header>
<h1><img src="http://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a"></h1>
<nav></nav>
</header>
<main></main>
However, when a vertical scrollbar is introduced, a problem arises due to the fact that the scrollbar width is included in the width being checked for in the media query, resulting in a negative left
position for the menu when the screen width is between 800px and 800-x
px (where x
is the width of the scrollbar). You can see this in the below Snippet (you'll probably need to view it full screen) by resizing your browser to slightly less than 800px - the right border of the menu gets a few pixels closer to the logo and the red edge of the menu is cropped.
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:101%;}
header{
align-items:center;
background:#eee;
border-bottom:1px solid #999;
display:flex;
height:100px;
left:0;
justify-content:center;
padding:0 10px;
position:fixed;
right:0;
top:0;
}
h1{
height:60px;
position:relative;
width:calc(100% - 40px);
max-width:740px;
z-index:2;
}
img{
height:100%;
width:auto;
}
nav{
background:#eee;
border-right:1px solid #999;
bottom:0;
left:0;
position:fixed;
top:0;
width:300px;
z-index:1;
}
@media (min-width:801px){
nav{
border-left:1px solid #999;
left:calc((100% - 800px) / 2)
}
}
nav::before{background:#ecc;bottom:0;content:"";left:0;position:absolute;top:0;width:10px;}
nav::after{background:#999;content:"";height:1px;left:0;position:absolute;right:0;top:99px;}
main{background:#ddf;border-left:1px solid #99c;border-right:1px solid #99c;height:100vh;min-height:100%;margin:0 auto;width:100%;max-width:800px;}
<header>
<h1><img src="http://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a"></h1>
<nav></nav>
</header>
<main></main>
I understand what's happening and why it's happening but my question is: is there any way, using CSS alone, to prevent it from happening? I've tried using viewport units instead of percentages but that creates the problem in reverse; at certain screen widths, the logo moves a bit further over to the left, away from the menu's right border. If all browsers had identical scrollbar widths or allowed for custom styling of scrollbars, it would be easy to get around this but, unfortunately, neither is the case.
08/09/16: I've accepted my own answer for now as it was the best JavaScript solution I could come up with but I'm still on the hunt for a CSS solution.
Upvotes: 16
Views: 4453
Reputation: 1030
If you are accepting JS solutions...
You might want to get the Real Width
instead the Scroll Bar Width
with the function below.
function getRealWidth()
{
var windowWidth = 0;
if (typeof(window.innerWidth) == 'number')
{
windowWidth = window.innerWidth;
}
else
{
if (document.documentElement && document.documentElement.clientWidth)
{
windowWidth = document.documentElement.clientWidth;
}
else
{
if (document.body && document.body.clientWidth)
{
windowWidth = document.body.clientWidth;
}
}
}
return windowWidth;
}
Upvotes: 0
Reputation: 6796
For the benefit of anyone who comes across this question and is open to a JavaScript solution, here's a very simple one that forces the nav
element's left
position to 0
when the viewport's width is greater than 800px
but the the body
element's width is less than that.
The downsides of this solution are using JavaScript to achieve something that (in my opinion, at least) we should be able to do with CSS alone and the overhead of using an onresize
listener. You can, however, remove that listener if you're only concerned about the menu being properly positioned onload
(see commented JavaScript in Snippet).
((b,n,setmenu)=>{
(setmenu=()=>n.dataset.forceleft=b.offsetWidth<800)();
window.addEventListener("resize",setmenu,0);
})(document.body,document.querySelector("nav"));
// Use above to set menu position onload and onresize
// Or use below to only set menu position onload
//(d=>d.querySelector("nav").dataset.forceLeft=d.body.offsetWidth<800)(document);
nav{
background:#eee;
border-right:1px solid #999;
bottom:0;
left:0;
position:fixed;
top:0;
width:300px;
z-index:1;
}
@media (min-width:801px){
nav[data-forceleft=false]{
border-left:1px solid #999;
left:calc((100% - 800px) / 2)
}
}
header{
align-items:center;
background:#eee;
border-bottom:1px solid #999;
display:flex;
height:100px;
left:0;
justify-content:center;
padding:0 10px;
position:fixed;
right:0;
top:0;
}
h1{
height:60px;
position:relative;
width:calc(100% - 40px);
max-width:740px;
z-index:2;
}
img{
height:100%;
width:auto;
}
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:101%;}
nav::before{background:#ecc;bottom:0;content:"";left:0;position:absolute;top:0;width:10px;}
nav::after{background:#999;content:"";height:1px;left:0;position:absolute;right:0;top:99px;}
main{background:#ddf;border-left:1px solid #99c;border-right:1px solid #99c;height:100vh;min-height:100%;margin:0 auto;width:100%;max-width:800px;}
<header>
<h1><img src="http://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a"></h1>
<nav></nav>
</header>
<main></main>
Upvotes: 0
Reputation: 1291
/* Alexander Gomez's getScrollBarWidth shorten version Josh Bambrick from:
http://stackoverflow.com/questions/986937/how-can-i-get-the-browsers-scrollbar-sizes
*/
function getScrollBarWidth() {
var $outer = $('<div>').css({
visibility: 'hidden',
width: 100,
overflow: 'scroll'
}).appendTo('body'),
widthWithScroll = $('<div>').css({
width: '100%'
}).appendTo($outer).outerWidth();
$outer.remove();
return 100 - widthWithScroll;
};
/* Tested on Chrome 51.0.2704, Firefox 40.0.2, Internet Explorer 11 */
$(document).ready(function() {
var mydelta = 800; /* your 'main' divs width */
var delta_mq = mydelta + 1;
delta = mydelta - getScrollBarWidth();
var h1_logo = delta - 40;
var $appended = '<style>h1{max-width:' + h1_logo + 'px} main{max-width:' + mydelta + 'px} @media (min-width:' + delta_mq + 'px){main{left:calc((100% - ' + delta + 'px) / 2);} nav{border-left:1px solid #999;left:calc((100% - ' + delta + 'px) / 2); } }';
$('body').append($appended);
});
* {
box-sizing: border-box;
margin: 0;
padding: 0
}
/*
'*' selector is depreciated cause it's slow
use normalize.css instead
And i removed your css media and appended them to the
page using jquery after getting scrollbar width
*/
header {
align-items: center;
background: #eee;
border-bottom: 1px solid #999;
display: flex;
height: 100px;
left: 0;
justify-content: center;
padding: 0 10px;
position: fixed;
right: 0;
top: 0;
}
h1 {
height: 60px;
position: relative;
width: calc(100% - 40px);
max-width: 750px;
z-index: 2;
}
img {
height: 100%;
width: auto;
}
nav {
background: #eee;
border-right: 1px solid #999;
bottom: 0;
left: 0;
position: fixed;
top: 0;
width: 300px;
z-index: 1;
}
main { /* position with 100px top margin */
position: absolute;
top: 100px;
}
header { /* chrome fix */
z-index: 1;
}
nav::before{background:#ecc;bottom:0;content:"";left:0;position:absolute;top:0;width:10px;}
nav::after{background:#999;content:"";height:1px;left:0;position:absolute;right:0;top:99px;}
main{background:#ddf;border-left:1px solid #99c;border-right:1px solid #99c;height:100vh;min-height:100%;margin:0 auto;width:100%;max-width:800px;}
<header>
<h1 class=logo>
<img src="http://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a">
</h1>
<nav></nav>
</header>
<main></main>
Upvotes: 1