Calculating width of scrollbar and using result in calc() css - javascript

I want to calculate the width of the scrollbar so that I use the result in a CSS calc() declaration.
At the moment, I assume that the width of the scrollbar is always 17px, like this:
body {
width:calc(100vw - 17px);
}
.container {
max-width:calc(100vw - 17px);
}
The problem with this is when you choose a different browser zoom %, the width of the scrollbar changes. So I want to use the result of the calculation to do something along these lines:
body {
width:calc(100vw - CALCULATED SCROLL-BAR WIDTH);
}
.container {
max-width:calc(100vw - CALCULATED SCROLL-BAR WIDTH);
}
EDIT: I've now solved the problem with the help of this question
The JavaScript used to calculate the scrollbar width (though, I have found you require an interval to get it to autoupdate):
function getScrollbarWidth() {
var outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
var widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
var inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
var widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
return widthNoScroll - widthWithScroll;
}
My code (which is used to embed the result of the function into a CSS calc() declaration).
$('body').css({
'width':'calc(100vw - ' + getScrollbarWidth() + 'px)'
});
$('.container').css({
'max-width':'calc(100vw - ' + getScrollbarWidth() + 'px)'
});

Actually, you can get the scrollbar width just with css and custom properties (and completely without javascript):
body {
--scrollbar-width: calc(100vw - 100%);
}
Then you can use this variable in a child element like this:
.container {
max-width: calc(100vw - var(--scrollbar-width));
}
This is because 100vw is always the inner width of the view, but the 100% of the body does not include the scrollbar.

Expanding jonas_jonas's answer, it can work but if .container must have the same width as the body.
If that's not the case, even so you can make it work with vanilla JS, defining a CSS property like this
document.body.style.setProperty(
"--scrollbar-width",
`${window.innerWidth - document.body.clientWidth}px`
);
And then you can use it in CSS
.container {
max-width: calc(100vw - var(--scrollbar-width));
}

Why you need so much code to do that?
The easy way with plain javascript it's:
$('body').css({
'width':'calc(100vw - ' + (window.innerWidth - document.body.clientWidth) + 'px)'
});

Related

Using Vanilla JS to add inline CSS property and fallback value

The problem I am trying to solve is basically the issue with Safari iOs and Vh calculations.
Here is the function:
function overflowHandler() {
const header = document.querySelector('.header-container').getBoundingClientRect().height;
const regionTop = document.querySelector('.region-top').getBoundingClientRect().height;
const totalHeaderHeight = header + regionTop;
if (window.innerWidth < 1080) {
flyout.style.height = 'calc(100vh - ' + totalHeaderHeight + 'px)';
}
}
But I need to be able to also pass in height: webkit-fill-available as a style as well to fix the bug on iOS where the user can't scroll completely to the bottom of the nav.
I've tried using element.style.cssText to set both height properties, but whatever is the rightmost value is what gets rendered in the browser.
Example of what I mean would be:
element.style.cssText = 'height: calc(100vh - ' + totalHeaderHeight + 'px); height: -webkit-fill-available;'
would render as height: -webkit-fill-available;
I am at a loss on any other way to make it work or how else to ensure the full height of the menu can be scrolled on iOS.
Use element.setAttribute('style', 'height: -webkit-fill-available;')

jQuery issue: body overflow vs body margin

Good day everyone!
When I click on a div I want overflow of body to become hidden and add a right margin to body of the scrollbar width. Here is my jQuery:
var getScrollbarWidth = function () {
var scrollElement = document.createElement("div"); // Create element
scrollElement.addClass("scrollbar-element"); // Apply predefined style
document.body.appendChild(scrollElement); // Add element to page
var scrollbarWidth = scrollElement.offsetWidth - scrollElement.clientWidth; // Subtract width without scrollbar from width with scrollbar
document.body.removeChild(scrollElement); // Remove element from page
return scrollbarWidth;
};
$(".thumbnail").on("click", function () {
$(".project-wrap").addClass("project-open");
$("body").attr("margin-right", getScrollbarWidth() + "px");
$(".project-button").attr("margin-right", getScrollbarWidth() + "px");
$("body").addClass("body-overflow");
}
And css:
.body-overflow
overflow: hidden !important
.project-open
display: block !important
The issue is that if "$("body").addClass("body-overflow");" is in the end, it doesn't work, but if it is on the first line of thimbnail click function, then body margin is not applied.
Try to store your scrollbar width in a variable before you add the body-overflow class.
Like:
$(".thumbnail").on("click", function () {
var scrollbarWidth = getScrollbarWidth();
$("body").addClass("body-overflow");
$("body").attr("margin-right", scrollbarWidth + "px");
$(".project-wrap").addClass("project-open");
$(".project-button").attr("margin-right", scrollbarWidth + "px");
});

Proportionally scale website to fit browser window

What would be an elegant solution to proportionally scale and center an entire website to fit a browser window (and updating as it's re-sized)
Assume the base layout is 720x500px
Content should proportionally scale to fit, and then re-center.
Essentially, operating like this Flash plugin: http://site-old.greensock.com/autofitarea/ (though base size is known)
Site will contain several different types of elements in that 720x500 area... ideal solution would just scale the whole thing, not needing to style each individual element (in case it matters- images will be SVG and so scaling should have no negative affect on resolution)
Depending on the browsers you need to support (IE9+), you could achieve that with simple CSS transform.
See an example (using jQuery) in this jsfiddle
var $win = $(window);
var $lay = $('#layout');
var baseSize = {
w: 720,
h: 500
}
function updateScale() {
var ww = $win.width();
var wh = $win.height();
var newScale = 1;
// compare ratios
if(ww/wh < baseSize.w/baseSize.h) { // tall ratio
newScale = ww / baseSize.w;
} else { // wide ratio
newScale = wh / baseSize.h;
}
$lay.css('transform', 'scale(' + newScale + ',' + newScale + ')');
console.log(newScale);
}
$(window).resize(updateScale);
If you need backwards compatibility, you could size everything in your site with % or em, and use a similar javascript to control the scale. I think that would be very laborious though.
One solution I'm using is working with a container in which I put an iframe that's being resized to fit as much available screen as possible without losing it's ratio. It works well but it's not completely flexible: you need to set dimensions in your content page in % if you want it to work. But if you can manage your page this way, I think it does pretty much what you want.
It goes like this. You create a container html page that's basically only styles, the resize script and the iframe call. And you content goes into the iframe page.
<style>
html, body
{
border: 0px;margin: 0px;
padding:0px;
}
iframe
{
display: block;
border: 0px;
margin: 0px auto;
padding:0px;
}
</style>
<script>
$(document).ready(function(e){
onResizeFn();
});
$(window).resize(function(e){
onResizeFn();
});
// this stretches the content iframe always either to max height or max width
function onResizeFn(){
var screen_ratio = 0.70 // this is your 720x500 ratio
if((window.innerHeight/window.innerWidth) > screen_ratio){
var theWidth = window.innerWidth
var theHeight = (window.innerWidth*screen_ratio);
} else {
var theHeight = window.innerHeight;
var theWidth = (window.innerHeight/screen_ratio);
}
document.getElementById("your_iframe").width = theWidth + "px"
document.getElementById("your_iframe").height = theHeight + "px"
}
</script>
// And then you call your page here
<iframe id='your_iframe' src='your_content_page' scrolling='no' frameborder='0'"></iframe>

JavaScript: Get window width minus scrollbar width

Ok, I thought this would be really simple, but it's turning out not to be. I think I'm just messing something up in my HTML/CSS, but here goes.
I have a basic page like so:
HTML
<!DOCTYPE html>
<html>
<head>
<link href='test2.css' rel="stylesheet" type="text/css" />
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="test2.js"></script>
</head>
<body>
<div id="scroll"></div>
</body>
</html>
test2.css
* {
padding: 0;
margin: 0;
}
html, body {
height: 100%;
width: 100%;
}
#scroll {
height: 100%;
width: 100%;
overflow: scroll;
background-color: black;
}
test2.js
$(document).ready(function() {
// my resolution is 1440x900
alert('innerwidth should be 1425');
// all of these return 1440
alert('body innerwidth: ' + $('body').innerWidth());
alert('document width: ' + $(document).width());
alert('window width: ' + $(window).width());
alert('scroll div innerwidth: ' + $('#scroll').innerWidth());
alert('document.documentElement.clientWidth: ' + document.documentElement.clientWidth);
alert('document.documentElement.scrollWidth: ' + document.documentElement.scrollWidth);
});
So I've got one element on the page... a div that takes up the entire screen, or rather it should be taking up the entire screen minus the scrollbars. Now, I've been doing some snooping on how to grab the width and height of a page without the scrollbars, but unfortunately, none of them return the proper value... which makes me believe I'm missing the boat in my HTML or CSS.
I looked at the following:
jquery - how to get screen width without scrollbar?
how to get the browser window size without the scroll bars
So what I need is for a method to return the value of my viewable screen minus the respective scrollbar value... so for my width, my value should be 1425 because the scrollbar is 15 pixels wide. I thought that's what innerWidth's job was, but apparently I'm wrong?
Can anyone provide any insight? (I'm running Firefox 24.)
EDIT
To add some background, I've got a blank page. I will be adding elements one by one to this page, and I need to use the width of the page when calculating the sizes for these elements. Eventually, this page will grow and grow until the scrollbar appears, which is why I'm trying to force the scrollbar there from the start, but apparently, that still doesn't do anything.
EDIT2
Here's something even more interesting... if I do document.getElementById('scroll').clientWidth, I get the proper innerWidth, but if I do $('#scroll').width() or $('#scroll').innerWidth(), they both return the max resolution... sounds like a jQuery bug.
I got this somewhere and would give credit if I knew where, but this has been succesfull for me. I added the result as padding when setting the html overflow to hidden.
Problem is that the scrollbar is a feature of the browser and not the web page self. Measurement should be done dynamically. A measurement with a scrollbar and a measurement without a scrollbar will resolve into calculating the difference in width.
Found the source: http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
scrollCompensate = function () {
var inner = document.createElement('p');
inner.style.width = "100%";
inner.style.height = "200px";
var outer = document.createElement('div');
outer.style.position = "absolute";
outer.style.top = "0px";
outer.style.left = "0px";
outer.style.visibility = "hidden";
outer.style.width = "200px";
outer.style.height = "150px";
outer.style.overflow = "hidden";
outer.appendChild(inner);
document.body.appendChild(outer);
var w1 = inner.offsetWidth;
outer.style.overflow = 'scroll';
var w2 = inner.offsetWidth;
if (w1 == w2) w2 = outer.clientWidth;
document.body.removeChild(outer);
return (w1 - w2);
}
var htmlpadding = scrollCompensate();
The correct answer is in this post marked as accepted:
CSS media queries and JavaScript window width do not match
This is the correct code:
function viewport() {
var e = window, a = 'inner';
if (!('innerWidth' in window )) {
a = 'client';
e = document.documentElement || document.body;
}
return { width : e[ a+'Width' ] , height : e[ a+'Height' ] };
}
Discovered a very hacky solution... by adding this before my alerts in test2.js, I get the proper width:
var p = $('body').append('<p style="height: 100%; width: 100%;"></p>');
alert(p.width());
$('body').remove('p');
And consequently, all of the alerts now have the proper width. I also don't even need overflow-y in the CSS if I do it this way. Curious why this solves it...
The real answer should be keeping the HTML and CSS as is, then using document.getElementById('scroll').clientWidth. Using clientWidth gets the viewable area minus the scrollbar width.
The correct width of the page is given by $(document).width().
Your problem is that you're using a scroll within the div (overflow: scroll).
Using $(document).width() the returned value is already discounting the visible width of the scroll, but how do you put a scroll within the div value returned is no longer the same.
As the width of the scroll is not standard and varies from system to system and browser to browser, it is difficult to solve.
I suggest you remove the scroll of the div and let the browser manage this by default in the body, then yes you have the correct width.

Dynamically make a child container take available width inside variable width parent

I have a container which wraps around three floated containers, the wrapping container has a variable width and the left most inner container has a width of 100px and the right most inner container has a width of 500px. The center container does not have a set width, but should take up as much space as possible that remains.
<style type="text/css">
#outerContainer div:nth-child(1) {float: left; width: 100px}
#outerContainer div:nth-child(2) {float: left}
#outerContainer div:nth-child(3) {float: right; width: 500px}
</style>
<div id="outerContainer">
<div>left most inner container</div>
<div>center container</div>
<div>right most inner container</div>
</div>
The dynamic center container has a few styles applied to it which make it's content overflow: hidden and ellipsis for presentation purposes.
<style type="text/css">
#outerContainer div:nth-child(1) {
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
</style>
I'm not sure what the solution is to dynamically scale the width of this inner element using ONLY css. Here's my JavaScript solution which works, but I'd like to cut it out as it seems excessive.
NS.textWidth = function(sourceSel){
var sourceSel = sourceSel,
html_org = $(sourceSel).html(),
html_calc = '<span>' + html_org + '</span>';
//Wrap contents with a span.
$(sourceSel).html(html_calc).css({width:'100%'});
//Find width of contents within span.
var width = $(sourceSel).find('span:first').width();
//Replace with original contents.
$(sourceSel).html(html_org);
return width;
};
adjustContainerWidth();
$(window).bind('resize', function(e){
clearTimeout(c.resize_timeout);
c.resize_timeout = setTimeout(function() {
adjustContainerWidth();
}, 200);
});
function adjustContainerWidth() {
var winW = parseInt($(window).width());
var firstContainer = parseInt($('#outerContainer div:nth-child(1)').width());
var lastContainer = parseInt($('#outerContainer div:nth-child(3)').width());
var availW = winW - firstContainer - lastContainer;
var textW = NS.textWidth('#outerContainer div:nth-child(2)');
if (availW > 40 && availW < textW) {
$('#outerContainer div:nth-child(1)').css({ width : availW + 'px' });
} else {
$('#outerContainer div:nth-child(1)').css({ width : textW + 'px' });
}
}
pure css http://jsfiddle.net/Za8RF/
This is, currently, untested but I think the following should work. Effectively find the width of the parent, the width of the siblings and then subtract one from the other:
var that = $(this),
parentWidth = that.parent().width(),
siblingsWidth = 0;
that.siblings().each(
function(){
siblingsWidth += $(this).outerWidth();
});
that.width(parentWidth - siblingsWidth);
JS Fiddle demo.
I've made the #outerContainer element 1000px wide, just to ensure that all elements have space to fit side-by-side in the demo.
I've also corrected your CSS a little; CSS is one-based, not zero-based like programming languages. So #outerContainer div:nth-child(0) wasn't matching, or styling, any elements.

Categories

Resources