简体   繁体   中英

Using CSS to scale a child to fit within a parent

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 margin s 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:

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM