Using CSS to scale a child to fit within a parent - html

Is it possible to scale a child (of known size) to match the width of it's parent (of unknown width)?
The child's size will be wider that it's parent. I need to find a way to scale it down so that it displays the entire child within the bounds of the parent.
I am hoping to avoid using javascript if possible.
Here is the code:
.a-very-wide-child {
/* the exact size of the child is known */
width: 3000px;
height: 500px;
/* I am trying to scale this element to match the width of its parent. */
/* I am thinking probably what I need is a value for [HERE]. I am open
to other suggestions though. */
transform: scale(calc(/* [HERE] */));
transform-origin: top left;
/* I am NOT wanting to use width rules to resize the child. */
/* no: width: 100%; */
/* no: max-width: 100%; */
}
.a-small-parent {
/* has no style rules of note */
/* the width of the parent is not known */
/* the height of the parent is not known exactly, but known to be tall
enough to not be of concern */
}
/* Below is just styling and is safe to ignore. */
.a-small-parent {
border: 5px solid teal;
}
.a-very-wide-child {
background-image: linear-gradient(45deg, #888, transparent);
border: 5px solid gold;
}
.container{
width: 40vw;
}
<div class="container">
<div class=a-small-parent>
<pre class=a-very-wide-child>
<img src="https://picsum.photos/400/500" alt="">
</pre>
</div>
</div>

Off late, I have been fiddling with all kinds of element size and font scaling for responsive behavior and knew I had already created something to solve your issue (see Codepen links at the end).
To start with the downside: you cannot avoid using at least some Javascript as you will need a unitless scale factor for the CSS transform: scale(..) to function properly. And for responsiveness purposes, you will need to know the current clientWidth of the parent container. This is also true for container queries and their respective units. With CSS you just cannot strip units off a property value.
Equation for the scale factor: parent.clientWidth / child.Width (unitless).
When scaling down with transform: scale(..), HTML will continue to use the original space the element occupied in the document causing a (huge) space surrounding the element. Fiddling with transform-origin will not solve that issue, but with negative margins we can 'remove' the excess space and snuggly fit our element in its parent. The demo assumes default transform-origin: center, simply meaning that below equation is true for all four margin properties.
Equation for margin space offset: offset = -1px * (childSize - scaled childSize) / 2 (with 'px'-unit conversion). Because of the current issue we can suffice to address width values only, so childSize reads childWidth.
The snippet uses CSS custom variables in all equations for easy manipulation and testing. The code is heavily commented, so I expect little surprises there. Let me know if I need to elaborate on something.
Be aware that when slowly resizing the browser you will sometimes see blank, single pixel lines above/aside the scaled element. This is not the result of the above equation, but due to rounding issues I have not taken into account. With division in equations you will get decimal values eventually causing partial pixels. Rule of thumb: part of a pixel is a full pixel. This would require additional Javascript as CSS does not round(..), ceil(..) or floor(..).
Some Codepens with variations on scaling I have created:
a web page with a list of scaled down <iframe> elements made to look like cards Codepen: Runtime scaling cards, CSS only
and Codepen: SO75129247 (WIP) of a SO question I haven't gotten around to answer, incorporating the above Codepen as a nested <iframe>.
The snippet
// Assign event listeners to show scaling works on resize
window.addEventListener('load' , setVariables); // Initial run
window.addEventListener('resize', setVariables); // Reponsiveness scaling
// parent elements involved
const parents = document.querySelectorAll('.a-small-parent');
function setVariables() {
// Modify CSS custom variables to hold current element
// width/height per parent
parents.forEach((el) => {
el.style.setProperty('--parentClientWidth' , el.clientWidth);
el.style.setProperty('--parentClientHeight', el.clientHeight);
});
};
.a-small-parent {
/* has no style rules of note. CHECK! */
/* the width of the parent is not known. CHECK! */
/* the height of the parent is not known exactly, but known to be tall
enough to not be of concern. CHECK! */
/* [OPTIONAL] defined here, just for clarity */
--parentClientWidth : 0; /* Modified by JS */
--parentClientHeight: 0;
}
.a-very-wide-child {
/* the exact size of the child is known. CHECK! */
/* Define the size of the child element, unitless */
--childWidth : 3000; /* We need unitless values to */
--childHeight: 500; /* calculate scale and offset */
width : calc(var(--childWidth) * 1px); /* Could be hardcoded units */
height: calc(var(--childHeight) * 1px); /* like the original code */
/* Define the parent to child scale factors for the transform, unitless */
--scaleH: calc(var(--parentClientWidth) / var(--childWidth)); /* Horizontal */
/*
When in the below 'transform' functions scaleX(..) and scaleY(..)
are being used, two transform-scale variables must be declared and
used for scaling and margin offset calculation.
*/
--scaleV: var(--scaleH); /* For now, Horizontal/Vertical scales are equal */
/* I am trying to scale this element to match the width of its parent. */
/* I am thinking probably what I need is a value for [HERE]. I am open
to other suggestions though. CHECK! */
/* Scale the child element relative to current parent clientWidth and clientHeight */
transform: scale(var(--scaleH), var(--scaleV));
/* I am NOT wanting to use width rules to resize the child. CHECK! */
/* no: width: 100%; */
/* no: max-width: 100%; */
/*
'transform: scale(..)' scales an element but leaves the original space the element
occupied intact. Essentially, leaving a big open space after scale down, or a burst out
of the parent when scaling up.
Assuming the element is scaled down and uses default 'transform-origin: center'
we need to calculate margin offsets to correct the gaps:
T/B: offset = -1 * (childHeight - scaled childHeight) / 2
R/L: offset = -1 * (childWidth - scaled childWidth) / 2
- childHeight/childWidth offset as their values differ
- multiplied by -1 as we need a negative offset to shrink the gap
- divided by 2 as we must shrink either side (Top and Bottom, Right and Left)
The sign of below 'offset-unit-multiplier' depends on the current scale factor
- negative when scale factor < 1
- positive when scale factor > 1
This requires additional Javascript to determine (not implemented).
*/
/* Assumed to be 'negative' in this demo: always scaling down */
--offset-unit-multiplier: -1px;
--offsetTB: calc(var(--offset-unit-multiplier) * (var(--childHeight) - var(--scaleV) * var(--childHeight)) / 2);
--offsetRL: calc(var(--offset-unit-multiplier) * (var(--childWidth) - var(--scaleH) * var(--childWidth)) / 2);
margin: var(--offsetTB) var(--offsetRL);
/*
Moved down as it is not needed. However, when assigned here,
the calculations for the margin offset must be changed accordingly.
This would require some testing/fiddling to find out what works best.
*/
/* transform-origin: top left;/* Obsolete */
}
/* Below is just DEMO styling and is safe to ignore. */
* {
box-sizing: border-box;
outline: 1px dashed; /* for debugging */
}
pre { margin: 0 }
img { height: 100%; object-fit: cover }
.a-small-parent { border: 5px solid teal }
.a-very-wide-child { border: 5px solid red; background-image: linear-gradient(45deg, #888, transparent) }
.container { width : 40vw }
<div class="container">
<div class=a-small-parent>
<pre class=a-very-wide-child>
<img src="https://picsum.photos/400/500" alt="">
</pre>
</div>
</div>

Related

Need to use position:fixed but still need element in flow. How to do it?

Scenario:
In HTML, I have 2 element (top bar and images). The top bar need to be
at position:fixed (which will break the flow, I understand that). And
the 2nd element has margin-top to push down the image after the
"top bar". This has no issue until I minimised my browser width, the
content in the "top bar" push the container height and overlap the 2nd
element, which is the image. And this look ugly.
Anyway to have the 2nd element in flow with the 1st element, so that no matter how I minimised my browser width, the 2nd element is
smart enough to push down.
Code: CSS
.TopBar { /* 1st Element */
background-color: #000000;
opacity: 0.5;
width: 100%;
position:fixed;
padding:10px;
}
.TopBar > div {
color:white;
}
.carousel { /* 2nd Element */
display: inline-block;
margin-top:73px;
}
.carousel_img {
width: 100%;
}
Problem:
As you already know, you can't force position:fixed to flow, so there isn't an answer to your question to do it the way you want.
But the way you describe the problem, it's about supporting different browser sizes. If that's the case, then it sounds to me as if media queries are the answer to your problem.
CSS supports #media { ... } blocks, which allow you to specify styles that only come into play at certain browser sizes. So in order to solve your problem, you need to find out what browser width causes the layout to change (resize very slowly; it will flip out at a specific size), and write a media query that changes your stylesheet for sizes lower than that.
Without (a lot) more detail of your layout, I can't really give you specific code, but there are a lot of resources available online to learn about media queries if you don't already use them.
It's also worth noting that position:fixed can often be troublesome at small browser sizes, so much so that a lot of mobile browsers deliberately didn't even support it for some time. That's changed now, but it can still cause layout gremlins, so you may want to use the media query to switch it off entirely in low-width browsers.
Respond to answer given by Spudley on using the #media to solve the issue, I have try to find some page that has the effect of "fixed" & overflow element, and inspect the code by viewing it through web editor. And this is what I get. I slowly delete all the CSS and related element one by one till I got the "fixed" not working. And while the is still set on position:relative, there is a CSS that attached to it, which when I remove it, the "fixed" effect was gone.
reference URL:
https://www.w3schools.com/css/css3_colors.asp
I filter the source file:
https://drive.google.com/drive/folders/0BzbdjY-H_HzZTC1Rci1nY0F4VFU?usp=sharing
Screen Capture of the coding that solve my problem (I guess)
Click Here to see the screen shot
If I understand what you want to achieve, there's a workaround to achieve similar results.
First, you effectively can't make your TopBar behaving like a flowing bloc element with position: fixed. So, let's make static.
The "fixed" behaviour will be provide by setting the body properties
body {
/* NOTICE the vertical flex box, makes the height adjust
automaticaly (no need to toggle)*/
display: flex;
flex-direction: column;
max-height: 100vh; /* Restrain the body to the window height */
overflow-y: hidden; /* Prevent scrollbar on body */
}
.TopBar {
display: block; /* Blocks have 100% widht by default, don't need to
specify */
padding: 10px;
position: static; /* No need to specify, it's the default value */
/* Helpers to make the TopBar easier to track */
background-color: #000000;
opacity: 0.5;
/* This is not part of the solution, it's only to make the height inversely proportional to window width
e.g. make it grow while the other decrease
*/
height: calc(200px - 10vw);
}
/*
Just under the TopBar, lets place a container element that will act
as scrolling window
*/
.container {
height: 100vh; /* max-height is superfluous because of the overflow. */
/* This will simply make the scrolling on the container instead of the body */
overflow-y: scroll;
}
.TopBar {
/* 1st Element */
}
.TopBar > div {
color: white;
}
/* simply to display some text */
p {
width: 50%;
margin: 1em auto;
}
Place your carousel inside the container and voilà! No need for position nor z-index fiddling. TopBar and container are flowing and the former will "push" the later.
That being said, some media query adjustments wouldn't hurt. According to your picture, elements in your TopBar are inlines (inline or inline blocks). You should consider making them "block". Flex-boxes would also worth some consideration.
Hope this help

Set width:100% relative to another object for an object that needs to be fixed

Code: https://jsfiddle.net/gsnfzn35/3/
It's a bit funky to describe, click the toggle drawer button. A pull out drawer on the right shows up. That pull out drawer is one container, but has 2 key components. The first component is all the content at the top. The last component is a "fixed row" on the bottom:
<div class="scroll-fixed-row" style="width:100%;text-align: right">
<p>
FIXED FINAL ROW
</p>
</div>
This row SHOULD be the width of the pull out drawer, whatever that width is; NOT the width of the screen. Currently though, if you inspect element with the width:100%, you see that the width is the width of the screen, not the pull out drawer. Another way to see this is in the fact that when there's width:100%;text-align:right, the text is off screen, pulled to the right of the row that is too wide. Remove the width:100% and you can see the text again.
I'm guessing this is due to the fact that the scroll-fixed-row is fixed, and therefore, its taking width from the screen, not the pull out drawer itself. But this fixed is necessary, because that scroll-fixed-row needs to stay at the bottom even though the rest of the pull out drawer scrolls. Given that constraint, how can I set the width of the scroll-fixed-row to be the width of the pull out drawer, for any screen (full responsiveness) WITHOUT having to provide specific width in pixels based on media queries?
The reason I'm asking this is because I would like to divide the scroll-fixed-row into 2 "sections" using either a table and 2 <td width="50%"> or using Bootstrap grid and 2 <div class="col-xs-6"> in a row. In the current implementation (NOT in the Fiddle), the content in the 2nd grid just gets pushed off page (same issue now) because the table width is inheriting from the screen. I think I can figure that part out if someone can help me answer this question.
The width can be solved by using inherit instead of 100%, this will make a fixed element get the width of its parent, in your case .container.scroll. I noticed that you have padding added to parent, the inherited width will include paddings and so the fixed element will overlay the scrollbars.
Code:
.scroll-fixed-row {
position: fixed;
text-align: right;
background-color: white;
border-top: 1px solid black;
width: inherit; /* get width from parent */
bottom: 0; /* stick to bottom */
right: 0; /* fix offset caused by padding */
}
Another thing I noticed that in your code is that you are using margin-top: 70px on .scroll to offset it from the fixed red nav, this causes the the bottom part that is out of viewport to be invisible, especially the bottom scroll arrow. I've changed it to the following:
.scroll {
position: fixed;
top: 70px; /* offset from top (nav height) */
height: calc(100% - 70px); /* calculate height minus the top offset */
}
If you wanted to prevent the fixed element from overlapping the scrollbars, you could apply pointer-events: none and add another wrapper in the HTML that gets a 15px spacing like the content, for better consistency:
.scroll-fixed-row {
...
pointer-events:none; /* disables mouse functionality to enable scrollbar control */
}
.scroll-fixed-row .inner {
border-top:2px solid red;
background:lightblue;
margin:0 30px 0 15px;
pointer-events:auto; /* allows mouse functionality */
}
jsFiddle demo - scrollbar overlap: https://jsfiddle.net/azizn/guufj4a0/
jsFiddle demo - additional wrapper: https://jsfiddle.net/azizn/d6wwk51b/

html 100%- (one line)

I'm trying to use html to set the height of an iframe to 100% (which I have successfully done), but I also added an extra line of text at the top, so it's ~16px too tall (which requires a scroll bar). Is there a way to change the iframe to display something like height="100%-16"?
Using calc(), you would use the following: height: calc(100% - 16px);
Unfortunately, this method doesn't have full support across browsers: reference here.
Example here
If you’re trying to make the <iframe> fill the entire window except for the part with the text, use absolute positioning instead of setting specific dimensions:
#my-frame {
position: absolute;
top: 2em; /* Whatever height you like */
/* All the other ones are just distances from each side, not sizes */
left: 0;
right: 0;
bottom: 0;
}
This works for all elements and takes into account both border and padding.

Allowing a background to overflow?

I have a background such as the below:
Trouble is I want to set the width of the containing box to only cover up to the end of the white areas & hence the shadow should "overflow". I don't want to use an out container or padding as I am trying to avoid extra width; if the background overflows then the browser isn't going to create horizontal; scrollbars for this and that's exactly what I want.
Is there any way I can accomplish this or will I be forced to use the full width?
Backgrounds on <body> don't affect the scrollbars, so depending on what you need the shadow for you might be able to do something similar to this;
body {
background: url(http://i.stack.imgur.com/ftW5z.png) no-repeat center top;
margin: 0;
}
.container {
width: 1014px; /* inner space of shadow image */
margin: 35px auto; /* 35px matches size of top shadow */
}
Demo at http://jsfiddle.net/gjsxn/2/
You can likely achieve the same effect with another container and use of min-width and overflow hidden.

Alternative for background-size:cover in IE7+

I have a background image on the body of my web page. I have used background-size:cover so that the image stretches across the body whilst maintaining the aspect ratio of the image. I would like this to be the same for IE7 + IE8.
I have looked around and seen the following code:
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(
src='AutumnWinter_4.jpg',
sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(
src='AutumnWinter_4.jpg',
sizingMethod='scale')";
But this doesn't preserve the aspect ratio, which is really bad for the website we are aiming at.
Is there any way to do this? Without hitting up jQuery?
What's the reason of not using jQuery? You could load it in conditional comments for IE<8 only, so that for every other modern browser jQuery is not loaded.
Also consider that IE7 has a very low market share (2,52%, April 2012) so it can be acceptable to load ~ 25kb extra for that specific browser if this feature is so important for your site/application.
So, I've found this plugin for jQuery: https://github.com/louisremi/jquery.backgroundSize.js
A jQuery cssHook adding support for "cover" and "contain" to IE6-7-8, in 1.5K
See Github project page for more info.
backgroundSize.js will not actually stretch the bg image in IE7, it seems to just center it at the original size. See their demo and click on 'Check what IE6-7-8 users would normally see.'
#danRhul
I have read that backstretch will work in IE7+
Good luck!
You could just fake a background image with an actual image. It's a bit more HTML editing and certainly not ideal, but since when has handling IE ever been ideal?
<body>
<img id="mainBG" src="mainBG.jpg" />
<div id="content">
[ ... ]
Then style it accordingly
body{
position:relative;
}
#mainBG{
width:100%
position:absolute;
top:0px;
left:0px;
}
Should be cross-browser if I'm not mistaken.
I've used the following (http://css-tricks.com/perfect-full-page-background-image/) and it works well in ie7.
HTML:
<body>
<img class="bg" src="filename">
</body>
CSS:
.bg {
/* Set rules to fill background */
min-height: 100%;
min-width: 1024px;
/* Set up proportionate scaling */
width: 100%;
height: auto;
/* Set up positioning */
position: fixed;
top: 0;
left: 0;
}
#media screen and (max-width: 1024px) { /* Specific to this particular image */
img.bg {
left: 50%;
margin-left: -512px; /* 50% */
}
}
I know this is now an old question, but I thought I'd share a solution I came up with for anyone else who finds this question on google, like I did.
I was trying to get an image to cover a site's background and came across this question, however none of the solutions worked for me. I came up with this instead:
HTML: move the background image to an <img />, make it the first thing in your <body>.
<html>
<body>
<img class="background" src="kitty.jpg" />
<div class="content">
...
CSS: make the background appear under the content, set it's min-width/height to 100%.
html {
height: 100%
}
body .background {
position: absolute;
z-index: -1;
min-height: 100%;
min-width: 100%;
}
It's the min-height and min-width here that does the magic. Do not give the image a width and height in the HTML or CSS, or the aspect ratio will change.
The above will work for IE7 and IE8. If you would like to support IE6, you could set a centered image fallback like this:
CSS: If IE6, don't display the image, use a background image instead.
body {
_background: url("kitty.jpg") 50% top no-repeat;
}
body .background {
_display: none;
}
(N.B. If you don't like the underscore hack to target IE6, you could use conditionals instead – that's what the HTML5 Boilerplate does.)
After much trial and error, the best solution was guessing it!
The following worked for me.
body {
background-size:100%;
}
You have two options to achieve this with just CSS:
Use Object-fit: cover. The only problem with this is that it will not work in all browsers
If you want cross browser support, you can follow primitive CSS approach:
Position the image inside the container with absolute and then place it right at the centre using the combination:
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
Note:
Since transform ONLY works from IE9, you can make use of filters. Here is an answer for this.
Once it is in the centre, you can do,
// For vertical blocks (i.e., where height is greater than width)
height: 100%;
width: auto;
// For Horizontal blocks (i.e., where width is greater than height)
height: auto;
width: 100%;
This makes the image get the effect of Object-fit:cover.
Here is a demonstration of the above logic.
https://jsfiddle.net/furqan_694/s3xLe1gp/
Unfortunately, most solutions to this kind of problem either depend on css3 or ignore the native functionality of "cover" that preserves the original aspect ratio of the image. https://github.com/louisremi/background-size-polyfill is supposed to preserve ratio, but I could never get it to work completely when stretching the browser in certain ways (operator error, I'm sure :-) ). To solve this problem, I wrote a jquery script that I've tested on safari, chrome, ff and ie8+. You'll notice that you will have to use an img positioned absolutely instead of css background-image. Just add the bgImg as an id in the tag in html.
CSS:
.container { height: auto; overflow:hidden; position:relative;}
.container #bgImg { position:absolute; z-index:-1;}
You're image selector will have to be positioned absolutely to get it to sit behind the content. That means that you're parent container has to have position: relative and then overflow: hidden so that whatever overflows from the image (since you're maintaining ratio, some pieces of it inevitable will) is hidden. Be aware also that certain display tags in the parent container will break the hiding of the overflow.
JQUERY:
$(window).load(function () {
// only do this once and pass as function parameters, because chrome
// and safari have trouble not only with document.ready but window.resize
var img = new Image();
img.src = $("#bgImg").attr('src');
var $width_orig = img.width;
var $height_orig = img.height;
resizeBGImage($width_orig, $height_orig);
$(window).resize(function () {
resizeBGImage($width_orig, $height_orig);
});
});
function resizeBGImage($width_img_orig, $height_img_orig) {
// get dimensions of container
var $width_container = $('.container').outerWidth();
var $height_container = $('.container').outerHeight();
// calculate original aspect ratio and ratio of the container
var $imageratio = $width_img_orig / $height_img_orig;
var $containerratio = $width_container / $height_container;
var $wdiff = $width_container - $width_img_orig;
var $hdiff = $height_container - $height_img_orig;
// original size, so just set to original
if (($wdiff == 0) && ($hdiff == 0)) {
$("#bgImg").css('width', $width_img_orig);
$("#bgImg").css('height', $height_img_orig);
}
// if container is smaller along both dimensions than the original image,
// set image to container size
else if (($wdiff < 0) && ($hdiff < 0)) {
$("#bgImg").css('width', $width_img_orig);
$("#bgImg").css('height', $height_img_orig+1); // adding one because chrome can't do math
}
// if container is wider than long relatiave to original image aspect ratio
// set width to container width and calculate height
else if ($containerratio > $imageratio) {
$("#bgImg").css('width', $width_container);
// calculate height using orig aspect ratio and assign to image height
$("#bgImg").css('height', (($width_container * $height_img_orig) / $width_img_orig) + 1); // adding one because chrome can't do math
}
// else container is taller than long relatiave to original image aspect ratio
// set height to container height and calculate width
else {
// set the image height to container height
$("#bgImg").css('height', $height_container + 1); // adding one because chrome can't do math
// calculate width using orig aspect ratio and assign to image width
$("#bgImg").css('width', (($height_container * $width_img_orig) / $height_img_orig));
}
$("#bgImg").css('left', (($width_container - $("#bgImg").width()) / 2).toString() + 'px');
};
Note the use of $(window).load() instead of $(document).ready(). Chrome and safari seem to have issues with the latter since in those browsers, the background image may not be fully loaded when the DOM is. Using $(window).load() ensures all window elements are in place before the script runs.
Sounds like you need a 'shim' or 'polyfill' like Modernizer:
http://modernizr.com/docs/#html5inie