Resizing a web page dynamically - javascript

I am developing a bunch of small web applications that have an unknown window size as target. To solve this problem, I am developing very large layouts and scaling them according to the window size.
My solution however has an inconvenience. When I resize everything, things get a little bit out of place (mainly when scaling text) and it is noticeable. My code is very simple, everything in my page is absolutely positioned so I just get the scaling factor and apply it to all the positions and width / height of every div/img/span/input in the page. The code is as follows:
function resize()
{
var wh = $(window).height();
var h = maxHeight;
var ww = $(window).width();
var w = maxWidth;
var wProp = ww / w;
var hProp = wh / h;
if (wProp < hProp) prop = wProp;
else prop = hProp;
if (prop > 1) prop = 1;
console.log(prop);
$("div").each (applyNewSize);
$("img").each (applyNewSize);
$("span").each (applyNewSize);
$("input").each (applyNewSize);
}
//this is run when the page is loaded
function initializeSize (i)
{
this.oX = $(this).position().left;
this.oY = $(this).position().top;
this.oW = $(this).width();
this.oH = $(this).height();
if ($(this).css("font-size") != undefined)
{
this.oFS = Number($(this).css("font-size").split("px")[0]);
}
}
function applyNewSize (i)
{
if (this.oFS != undefined) $(this).css("font-size", Math.round(this.oFS * prop) + "px");
$(this).css("left", Math.round(this.oX * prop) + "px");
$(this).css("top", Math.round(this.oY * prop) + "px");
$(this).width(Math.round(this.oW * prop));
$(this).height(Math.round(this.oH * prop));
}
This problem has been tormenting me for the past week. Do you have any workaround or solution for this?

I recommend you to read about Responsive Web design.
It works putting % instead the exact pixels :
<div class="container">
<section>
THIS IS THE SECTION
</section>
</div>
CSS::
.container{
width: 80%; // 80% instead pixels
background: gainsboro;
border: 3px inset darkgrey;
height: 200px;
margin:auto;
text-align:center;
}
section{
width: 80%; // 80% instead pixels
height: 80%; // 80% instead pixels
background: darkgrey;
margin:auto;
}
Then you can use media queries as well, to reallocate the blocks or applying different styles on different widths :
example tutorial : http://css-tricks.com/css-media-queries/

Related

Incremental CSS Media Queries

Consider a stylesheet that resizes the page as the height diminishes using media queries and transform:
#media (max-height: 620px) {
body {
transform: scale(0.95);
}
}
#media (max-height: 590px) {
body {
transform: scale(0.90);
}
}
#media (max-height: 560px) {
body {
transform: scale(0.85);
}
}
// and so on ...
The page "zooms out" as the window height diminishes, allowing the full content to be displayed on smaller screens.
If I want to support screens smaller than 560px height, I need to add more media queries.
Notice that for each 30px lost in height, we call scale with 0.05 less in the input.
Question: Is it there a way to define incremental media queries using only css?
Follow Up: If a pure css solution is not available, what would be the simplest way of accomplishing such effect in vanilla JS?
Edit: Thank you all for posting different solutions to this problem. I appreciate your help. Your answers helped me learn how to improve my code.
This is not possible using CSS alone. You can do this using JS by adding a window.onresize function to watch for resizing and scaling the body of the document. This solution also scales dynamically so you do not need to worry about breakpoints or #media queries.
function updateScale() {
let viewScale = window.innerHeight / document.documentElement.scrollHeight;
document.body.style = 'transform:scale(' + viewScale + ')';
document.documentElement.scrollHeight = window.innerHeight;
}
window.onresize = function() {
updateScale();
}
updateScale();
body {
transform-origin: 0 0;
margin: 0;
padding: 0;
overflow: hidden;
}
#block {
background-color: black;
width: 300px;
height: 4000px;
}
<div id='block'></div>
#NathanFries is correct, this isn't something that is possible with native CSS.
CSS includes viewport units for percentage-based values for your width and height, but you can't pass that onto the scale() function.
You'd then need to tie this to some resize event listener.
Here is a quick example that might accomplish what you're looking to do:
// #see https://github.com/WICG/EventListenerOptions/blob/9dcca95/explainer.md#feature-detection
// #example `elem.addEventListener('touchstart', fn, supportsPassive ? { passive: true } : false);`
var checkSupportPassiveEvents = function() {
var supportsPassive = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function() {
supportsPassive = true;
},
});
window.addEventListener('testPassive', null, opts);
window.removeEventListener('testPassive', null, opts);
} catch (e) {}
return supportsPassive;
};
var supportsPassive = checkSupportPassiveEvents();
var mapRange = function(fn, inStart, inEnd, outStart, outEnd) {
if (outStart === void 0) {
outStart = inStart;
outEnd = inEnd;
inStart = 0;
inEnd = 1;
}
var inRange = inEnd - inStart,
outRange = outEnd - outStart;
return function(val) {
var original = fn((val - inStart) / inRange);
return outStart + outRange * original;
};
};
var linear = function(x) {
return x;
};
var minHeight = 320;
var maxHeight = 620;
var minScale = 0.45;
var maxScale = 1;
var screenHeightToScaleFactorInner = mapRange(linear, minHeight, maxHeight, minScale, maxScale);
var screenHeightToScaleFactor = function(height) {
if (height <= minHeight) {
return minScale;
} else if (height > maxHeight) {
return maxScale;
} else {
return screenHeightToScaleFactorInner(height);
}
};
window.addEventListener(
'resize',
function(e) {
var height = this.innerHeight;
this.document.body.style.transform = 'scale(' + screenHeightToScaleFactor(height) + ')';
},
supportsPassive ? { passive: true } : false
);
You may have to use vanilla JavaScript in this case.
So, each 30px is 0.05, or each 6px is 0.01 from 650px down.
It means an amount of 600px is 1 in scale and that each pixel is 0.01/6.
With all that information in mind, we can use resize event to calculate this:
window.addEventListener('resize', changeTransform); // when resize, call changeTransform
var bodyEl = document.querySelector('body');
function changeTransform() {
var scale = 1; // default scale
if (window.innerHeight < 650) { // if window height < 650px, do magic
scale = -0.83 + (0.05/30)*window.innerHeight;
}
bodyEl.style.transform = "scale(" + scale + ")"; // apply scale (1 or the calculated one)
}
Hope it can help you somehow.
For a CSS solution you can consider vh and vw unit but it won't be possible with scale since this one need a unitless value.
A different approach would be to use translateZ() and some perspective to simulate a scale animation.
Here is a basic example. The result isn't accurate as I simply want to demonstrate the trick. Run the snippet full page and adjust the window height and you will see the element scaling.
.box {
width:100px;
height:100px;
background:red;
transform:perspective(180px) translateZ(15vh);
transform-origin:top left;
}
<div class="box">
some text here
</div>
You simply need to find the correct calculation for the different values.

Parallax with zoom on scroll

I've tried to find similar posts about this but failed to do so. What I'm trying to do is set up a parallax background that has a moderate zoom upon scrolling. I have the parallax down, that was simple enough, but the zoom on scroll is causing me difficulties.
if ($(".zoomImage").length == 0)
{
console.warn("You're attempting to set hero images without an existing class. '.heroImage'");
return;
}
$(document).scroll(function(){
var scrollpos = $(this).scrollTop();
var screenHeight = $(window).height();
var screenWidth = $(window).width();
$(".zoomImage").each(function(){
var offset = $(this).offset().top;
// Only modify when top is at top of browser on screen.
if (offset < scrollpos && scrollpos < offset + screenHeight)
{
var heroEffectPerc = 100 + 25 * (scrollpos - offset) / (screenHeight * 1.0);
$(this).css("background-size", heroEffectPerc + "% auto");
}
});
});
This is where I'm doing the zoom for the image, the parallax is done in pure CSS as represented below. The issue I'm having is figuring out the mathematics to make sure that the image doesn't escape the edge of its parent when the screen gets excessively wide or tall and still achieve the same effect. 1:
CSS:
pageCanvas
{
position: relative;
background-repeat: no-repeat;
background-position: center;
background-size: auto 100%;
background-color: white;
display: block;
left: 0;
top: 0;
width: 100%;
height: 100vh;
}
pageCanvas.parallax
{
background-attachment: fixed;
}
.
<pageCanvas class="parallax zoomImage" style="background-image: url('./Images/DisplayBackground.png');">
<banner>
<header>
Company name
</header>
<description>
I don't want to<br><span style="margin-left: 200px;">advertise here.</span>
</description>
</banner>
</pageCanvas>
I've tried to get it working, but either have an issue with one of the following:
White background shows on too wide.
White background shows on too tall.
Image stretching.
Do I need to bring in the images origin ratio with this or something? If I figure out a solution prior to an answer given, I'll post it.
Although the original was in a namespace, I'm going to place this answer as if it were not because I hadn't specified. Either way, I found the solution.
The first step was finding the ratio of the original image;
$(".zoomImage").each(function(index){
var bg = $(this).css('background-image');
bg = bg.replace('url(','').replace(')','').replace(/\"/gi, "");
// get image size.
var tmpImg = new Image();
tmpImg.src=bg;
$(tmpImg).one('load', function(){
orgWidth = tmpImg.width;
orgHeight = tmpImg.height;
bgImageRatios[index] = orgHeight / (orgWidth * 1.0);
});
});
To make life easier, I placed them in an array that was global to the name space. This is so I don't have to A) keep finding the ratio of the image, and B) can access it similarly to initializing later on. It should be noted that this method would require being called again in the instance there is any more or less '.zoomImage' classes brought into instance, as the array will be incorrect at that point.
What I did next was place the original code that loops the class into a function.
function zoomImage(scrollpos, screenHeight, screenWidth)
{
//console.log(screenHeight);
$(".zoomImage").each(function(index){
var offset = $(this).offset().top;
if (offset < scrollpos && scrollpos < offset + screenHeight)
{
var heroEffectPerc = 100 + 25 * (scrollpos - offset) / (screenHeight * 1.0);
if ((bgImageRatios[index] * screenWidth / screenHeight) > 1)
$(this).css("background-size", heroEffectPerc + "% auto");
else
$(this).css("background-size", "auto " + heroEffectPerc + "%");
}
});
}
I put it into a function because it would have been placed into two separate locations otherwise. (that's just messy coding). I updated the image size as follows.
$(window).on("resize", function(){
var scrollpos = $(document).scrollTop();
var screenHeight = $(this).height();
var screenWidth = $(this).width();
pageCanvas.zoomImage(scrollpos, screenHeight, screenWidth);
});
$(document).on("scroll", function(){
var scrollpos = $(this).scrollTop();
var screenHeight = $(window).height();
var screenWidth = $(window).width();
pageCanvas.zoomImage(scrollpos, screenHeight, screenWidth);
});
The following sources helped me solve my answer:
Can I get div's background-image url?
How to get image size from URL
Credit is due to them.

Javascript Font-Resizer Overflowing Div

I've been working on a website (scibowltest.github.io) in which the font-size for an element is determined by javascript. If you go in to the website, it looks fine when displayed on a laptop/desktop with the browser at full width and height. However, if you resize the window to make the width smaller, the font starts to overflow the div in which it is supposed to be contained.
The following code is for the function font-sizer, which determines how big the font-size will be.
function fontSizer(text, box, scale) {
var textID = "#" + text;
var boxID = "#" + box;
var boxHeight = $(boxID).height();
var boxWidth = $(boxID).width();
var fontSize = 0;
if (boxHeight >= boxWidth) {
fontSize = (boxWidth * scale) + "px";
} else if (boxHeight < boxWidth) {
fontSize = (boxHeight * scale) + "px";
} else {}
var textHeight = $(textID).height();
var textWidth = $(textID).width();
if (textWidth > boxWidth) {
fontSize = ((fontSize * (scale * boxWidth)) / (textWidth)) + "px";
} else {}
$(textID).css("font-size", fontSize);
}
The function works based on the smaller dimension of the div (box) containing the text. However, the last if statement tries to compensate for those situations in which the text's width is greater than the div's width to try to shrink the fontsize so that the text's width is equal to the div's width times the scale.
Obviously, from looking at the website, this last if statement isn't working. If anyone could help, I would appreciate it!
For a pure CSS solution you can use some of the new length units that are intended exactly for your usecase - different screen sizes.
Play around with vw,vh,vmin,vmax and see what gives you the best results. I personally use vmin a lot.
It requires a bit of fiddling but would eliminate the use of javascript.
Also I used <wbr> in this example (although it should not be needed) - a "word break opportunity" to additionally prevent overflowing.
div {
width: 40vmin;
font-size: 8vmin;
border: 1px solid red;
resize: both;
text-align: center;
}
p {
width: 40vw;
font-size: 8vw;
border: 1px solid red;
resize: both;
text-align: center;
}
resize the screen to see the fonts scale:
<div>Start/<wbr>Stop</div>
<p>Start/<wbr>Stop</p>

Custom scroll bar flows out of screen

I'm trying to make my own scroll bar, and so far it's working fine, for this small exception.
When I reach the bottom of the page, the bar handle goes under the viewport.
Gif of what's happening:
I know it has to do with the CSS, but I'm unsure on how to set it correctly. Foundation's .off-canvas-content has a class added named .full-height, and the height property is added so that the scroll bar won't be tied to that element.
The scroll bar markup is added to div.content, which is where all the remaining content will be.
I'm trying to get the handle bar to stop at the bottom of the container, when the user has scrolled all the way of the bottom of the document, but haven't found a way to do this correctly.
CSS:
.scroll-container {
position: fixed;
right: 50px;
top: 0;
height: 100%;
width: 7.5px;
background-color: rgba(55,55,55,.3);
}
.scroll-bar {
position: relative;
top: 0;
height: 20%;
width: 100%;
background-color: #6A1B9A;
}
.full-height {
height: 100vh;
min-height: 100vh;
}
.content {
float: left;
width: 100%;
height: 100%;
display: block;
padding: 10px 20px;
overflow-y: scroll;
}
JS:
(function($) {
$.fn.scroller = function() {
var self = this,
scrollBarDrag = false,
docHeight = $(document).height();
var scrollContainer = document.createElement('div'),
scrollBar = document.createElement('div');
scrollContainer.className = 'scroll-container';
scrollBar.className = 'scroll-bar';
scrollContainer.appendChild(scrollBar);
self[0].appendChild(scrollContainer);
self.on('scroll', function() {
var top = $(this).scrollTop();
setScrollBarTop(top);
});
function setScrollBarTop (top) {
scrollBar.style.top = top + 'px';
}
};
})(jQuery);
I tried using plugins for this, but they don't simulate the scroll bar as intended (missing mouse wheel click and drag to scroll), so I decided to make my own, lightweight version of it. Any suggestions about using plugins, albeit appreciated, will be disregarded and not accepted as an answer.
With absolute positioning:
I think you forgot to account for the scrollbar's height. Lets say the scrollbar is 100px tall and your page is 500px tall, you are only able to move the scrollbar by 400px, not all 500.
Find out the difference between your scrollbar height and the document height, find the ratio of how they compare, and apply that to your new scrollbar position.
havent tested it, but something like;
var heightToWorkWith = docHeight - scrollBarHeight;
var ratio = heightToWorkWith / docHeight;
function setScrollBarTop (top) {
scrollBar.style.top = (top * ratio) + 'px';
}
Have found a solution regarding this, was quite a bit of trial and error, but managed to find it in the end. Hope it can be of use to some of you.
Edited it to a more revised version.
self.on('scroll', function() {
elHeight = self.height();
docHeight = $(document).height();
var sTop = self[0].scrollTop;
var sHeight = self[0].scrollHeight;
var sBHeight = $(scrollBar).height();
var ratio = (elHeight - $(scrollBar).height()) / elHeight;
var currentPosY = (sTop / (sHeight - docHeight)) * 100;
scrollBar.style.top = (currentPosY * ratio) + '%';
});
You can get scroll ratio by doing this:
(thumbHeight / containerHeight) + 1
containerHeight is not the scroll area height, but the actual overflow: hidden container.
When you get the scrollTop value just multiply it with your ratio. Like this:
thumbPosition.top = el.scrollTop * ratio + 'px';

Elements positioned at the bottom & cut-off from bottom

I have three blocks, I want them positioned at the bottom always, regardless of the viewport height, and when there's not enough height to show all of it, I want them to hide from the bottom, NOT the top.
I tired a flexbox solution: http://jsbin.com/kutipequxe/1/edit?css,output
.. it almost works, except on low resolutions, the blocks hide from top, bottom remains visible.
I also tried another solution: http://jsbin.com/ruhigijeba/1/edit?css,output
.. well this keeps the top always visible, but just hides the bottom altogether, including the other two blocks.
I even tried a JS solution:
var vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
var topHeight = document.getElementById('top').offsetHeight;
console.log('Viewport Height: ' + vh);
function getHeight(element) {
console.log(document.getElementsByClassName(element));
var offsetHeight = document.getElementsByClassName(element)[0].offsetHeight;
console.log('offsetHeight: ' + offsetHeight);
var marginTop = vh - (topHeight + offsetHeight);
console.log('marginTop: ' + marginTop);
document.getElementsByClassName(element)[0].style.marginTop = marginTop + "px";
}
getHeight("card-1");
getHeight("card-2");
getHeight("card-3");
... but it still hides the blocks from top!
try it with CSS media queries:
At the end of your CSS just add
#media screen and (max-height: 120px) {
div#top {
display: none;
height: 0px;
}
#main {
height: 100vh;
}
}
[edit] appearently thats not what oyu were asking for.
so... in your second jsbin example, add this to your .cards class:
position: sticky;
bottom: 0;
and to your #cards id:
overflow: hidden;
http://jsbin.com/zijedofija/1/
it does not work on chrome 35+ though: Why doesn't position: sticky work in Chrome?
my best bet would be to use a jquery plugin for chrome: https://github.com/filamentgroup/fixed-sticky
Ended up using Javascript and CSS media queries to achieve the desired results:
var vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
var topHeight = document.getElementById('top').offsetHeight;
function getHeight(element) {
var elementHeight = document.getElementsByClassName(element)[0].offsetHeight;
var offsetTop = vh - (topHeight + elementHeight);
var cardsContainerHeight = document.getElementById('cards').offsetHeight;
if (elementHeight < cardsContainerHeight) {
document.getElementsByClassName(element)[0].style.top = offsetTop + "px";
}
}
var resize = function(event) {
getHeight("card");
}();

Categories