CSS: make div width proportional to height - html

This is a little tricky to explain, but: I want a responsive-height div (height: 100%) that will scale the width proportional to the height (not vice versa).
I know of this method utilising a padding-top hack to make the height proportional to the width, but I need it to work the other way around. Having said that, I'm not hugely keen on the additional requirement of absolutely-positioned elements for the content in that method, so I realise I may well be asking for the moon on a stick here.
To help visualise, here is an image:
...and here is a jsFiddle, illustrating pretty much the same thing.
It is worth noting that I am already using the :before and :after pseudo-elements to vertically-align the content of the box I want to scale proportionally.
I would really enjoy not having to revert to jQuery, just because there's going to be an inherent requirement for resize handlers and generally more debugging all round... but if that's my only choice, then fiat.

I've been wondering about a pure-css solution to this problem for a while. I finally came up with a solution using ems, which can be progressively enhanced using vws:
See codepen link for full working demo and explanation:
http://codepen.io/patrickkunka/pen/yxugb
Simplified version:
.parent {
font-size: 250px; // height of container
height: 1em;
}
.child {
height: 100%;
width: 1em; // 100% of height
}

Oh,you could probably use that "padding-top" trick.
width: 50%;
height: 0;
padding-bottom: 50%;
http://absolide.tumblr.com/post/7317210512/full-css-fluid-squares
Or:
.square-box{
position: relative;
width: 50%;
overflow: hidden;
background: #4679BD;
}
.square-box:before{
content: "";
display: block;
padding-top: 100%;
}
http://codeitdown.com/css-square-rectangle/
The vertical padding in CSS is related to the width of the element, not the height.

The font solution requires that the height is known. I have found a solution for making an element proportional inside a parent div with unknown widths and heights. Here is a demo.
The trick I'm using is to have an image used as a spacer. The code explained:
<div class="heightLimit">
<img width="2048" height="2048" class="spacer"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAA
P///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
<div class="filler">
<div class="proportional">
</div>
</div>
</div>
So it is not the prettiest with two extra divs and a useless image. But it could be worse. The image element needs to have width and height with the desired dimensions. Width and height need to be as large as the maximum size allowed (a feature!).
The css:
.heightLimit {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: auto;
max-width: 100%;
overflow: hidden;
}
This element is to limit the height, but to expand horizontally (width: auto) although never beyond the parent (max-width). Overflow needs to be hidden because some children will protrude outside the div.
.spacer {
width: auto;
max-height: 100%;
visibility: hidden;
}
This image is invisible and scaled proportionally to the height, while the width is adjusted and forces the width of the parent to also be adjusted.
.filler {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
This element is required to fill the space with an absolutely positioned container.
.proportional {
position: relative;
width: 100%;
height: 0;
padding-bottom: 100%;
}
And here our proportional element gets a height proportional to the width with the familiar padding-bottom trick.
Unfortunately, there is a bug in Chrome and IE so if you modify the parent element using Javascript, such as in my demo, the dimensions will not be updated. There is a hack that can be applied to solve that, as shown in my demo.

You can use view height (vh) as the unity for the width.
Here is an example with the 20px margin you asked for.
.parent {
margin : 20px;
}
.child {
width: calc(100vh - 40px);
height : calc(100vh - 40px);
margin:0 auto;
background: red;
box-sizing:border-box;
padding:10px;
}
See the fiddle :
https://jsfiddle.net/svobczp4/

Based off of #kunkalabs's answer (which is really smart) I've come up with a solution that lets you preserve the inherited font-size.
HTML:
<div id='rect'>
<div id='content'>Text</div>
</div>
CSS:
#rect {
font-size: 1000%;
height: 1em;
width: 1em;
position: relative;
}
#content {
font-size: 10%;
}
So basically the font-size of #content is (100 / $rectFontSize) * 100 percent of the rectangle. If you need a definite pixel size for the rectangle, you can set the #rect's parent's font-size…otherwise just adjust the font-size until it's about where you want it to be (and enrage your designer in the process).

You can achieve that by using SVG.
It depends on a case, but in some it is really usefull. As an example - you can set background-image without setting fixed height or use it to embed <iframe> with ratio 16:9 and position:absolute.
For 3:2 ratio set viewBox="0 0 3 2" and so on.
Example:
div{width:35%;background-color:red}
svg{width:100%;display:block;visibility:hidden}
<div>
<svg viewBox="0 0 3 2"></svg>
</div>

On newer browsers, we can use aspect-ratio with a fixed height, and the width will be calculated accordingly.
img {
aspect-ratio: 1.2;
height: 250px;
max-width: 500px;
}
But the browser support for aspect-ratio is not good enough. I liked the SVG solution proposed by #Jakub Muda, except for the fact that it requires modifying the markup. I have moved the SVG to CSS by including it using content property. On newer browsers, it disables the SVG hack and switches to aspect-ratio property.
document.querySelector('.nav').addEventListener('click', function(e) {
var index = parseInt(e.target.dataset.index);
if (!index) {
return;
}
var elements = document.querySelectorAll('.box');
for (var i = elements.length; i > 0; i--) {
elements[i - 1].classList.toggle('hide', i !== index);
}
});
.wrapper {
max-width: 500px;
margin: 0 auto;
width: 100%;
height: 250px;
text-align: center;
background: green;
}
.box {
display: inline-flex;
position: relative;
max-width: 100%;
}
/* SVG Hack */
.box::before {
display: block;
line-height: 0;
max-width: 100%;
content: 'test';
}
[data-aspect-ratio="1"]::before {
content: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1' height='250'></svg>");
}
[data-aspect-ratio="2"]::before {
content: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 1' height='250'></svg>");
}
[data-aspect-ratio="3"]::before {
content: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 1' height='250'></svg>");
}
#supports (aspect-ratio1: 1) {
/* Modern browsers */
.box {
height: 100%;
background: green;
}
.box::before {
display: none;
}
[data-aspect-ratio="1"] {
aspect-ratio: 1;
}
[data-aspect-ratio="2"] {
aspect-ratio: 2;
}
[data-aspect-ratio="3"] {
aspect-ratio: 2;
}
}
.content {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.content>svg {
display: block;
width: 100%;
position: absolute;
top: 50%;
left: 50%;
height: auto;
transform: translate(-50%, -50%);
}
.nav {
text-align: center;
}
.hide {
display: none;
}
<!doctype html>
<html lang="en">
<head>
<title>Width proportional to height in CSS</title>
</head>
<body>
<div class="wrapper">
<div class="box" data-aspect-ratio="1">
<div class="content">
<svg viewBox="0 0 100 100" width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="96" height="96" style="fill:#DEDEDE;stroke:#555555;stroke-width:2"/><text x="50%" y="50%" font-size="18" text-anchor="middle" alignment-baseline="middle" font-family="monospace, sans-serif" fill="#555555">100×100</text></svg>
</div>
</div>
<div class="box hide" data-aspect-ratio="2">
<div class="content">
<svg viewBox="0 0 200 100" width="200" height="100" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="196" height="96" style="fill:#DEDEDE;stroke:#555555;stroke-width:2"/><text x="50%" y="50%" font-size="18" text-anchor="middle" alignment-baseline="middle" font-family="monospace, sans-serif" fill="#555555">200×100</text></svg>
</div>
</div>
<div class="box hide" data-aspect-ratio="3">
<div class="content">
<svg viewBox="0 0 300 100" width="300" height="100" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="296" height="96" style="fill:#DEDEDE;stroke:#555555;stroke-width:2"/><text x="50%" y="50%" font-size="18" text-anchor="middle" alignment-baseline="middle" font-family="monospace, sans-serif" fill="#555555">300×100</text></svg>
</div>
</div>
</div>
<div class="nav">
<button data-index="1">1</button>
<button data-index="2">2</button>
<button data-index="3">3</button>
</div>
</body>
</html>

Make the parent DIV behave like a table cell and align the child element vertically. No need to do any padding tricks.
HTML
<div class="parent">
<img src="foo.jpg" />
</div>
CSS
.parent { width:300px; height:300px; display:table-cell; vertical-align:middle; }

Related

Keep Element In Middle Of Excess Space With CSS calc() Function

I have a <main> HTML element that takes up 80% of the <body> element on a page and it has a max-width value of max-width: 1220px.
Outside of this <main> element I have a small SVG arrow - is it possible with the CSS calc() function to have this so it is always in the middle of the area outside the main element on the left hand side?
If I didn't have a max-width property I could just do left: 10% but this only works up until the main element hits it max-width.
The full code is below and I've managed to get it so it aligns with the left hand side of the main element, but I can't get it so it is halfway across the white space on the left hand side. I'm thinking it may not be possible in CSS.
Codepen: https://codepen.io/emilychews/pen/bGEJjwX
Note 1: When viewing the snippet below you'll need to view it full-page because of the max-width value.
Note 2: What I am trying to do is illustrated in the below image.
main {
position: relative;
margin: 0 auto;
width: 80%;
padding: 6rem 0;
background: red;
height: 100vh;
max-width: 1220px;
}
.down-arrow {
position: absolute;
width: 1rem;
height: 1rem;
bottom: 25vh;
/* THIS IS THE BIT I NEED HELP WITH */
left: calc((100% - 1220px) / 2);
}
<main>
<div class="row"></div>
</main>
<svg class="down-arrow" aria-hidden="true" viewBox="0 0 410.95 355.89">
<polygon fill="#000" points="205.47 354.89 410.08 0.5 0.87 0.5 205.47 354.89" /></svg>
Because the arrow is absolutely positioned, it makes it a lot more complicated to position it relative to the main element. Instead, you can achieve the same effect (if i'm understand what you're looking for correctly!) using a wrapping container and Flexbox (or default CSS to vertically and horizontally center the child elements, I just prefer flex).
What I did was wrap the main element and the arrow in a div, labeled with a class of container. This way, we can position the main and svg relative to each other while still maintaining the flow of the application.
Display flex automatically aligns child elements in a row, which puts the svg and main elements next to each other. Adding align-items and justify-content center ensures that everything remains vertically and horizontally centered. I removed the margin: 0 auto; from main and the absolute positioning from the svg since it's no longer necessary.
Pen with changes, or see below: https://codepen.io/xenvi/pen/OJMGweV
body {
margin: 0;
height: 100vh;
}
.container {
display: flex;
}
main {
position: relative;
width: 80%;
padding: 6rem 0;
background: red;
height: 100vh;
max-width: 1220px;
}
.down-arrow {
width: 1rem;
height: 1rem;
bottom: 25vh;
}
.arrow-container, .end-container {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
}
<div class="container">
<div class="arrow-container">
<svg class="down-arrow" aria-hidden="true" viewBox="0 0 410.95 355.89">
<polygon fill="#000" points="205.47 354.89 410.08 0.5 0.87 0.5 205.47 354.89" /></svg>
</div>
<main>
<div class="row"></div>
</main>
<div class="end-container"></div>
</div>
You need to use left: calc((100% - 1220px)/4);. From 100% we remove the width to get the white space. Then we divide by 2 to get only the left part and we divide again by 2 to get half of it.
You can also use min() to make it working for small screen too:
main {
position: relative;
margin: 0 auto;
width: 80%;
padding: 6rem 0;
background: red;
height: 100vh;
max-width: 1220px;
}
.down-arrow {
position: absolute;
width: 1rem;
height: 1rem;
top: 25vh;
left: calc((100% - min(1220px,80%))/4);
transform:translate(-50%); /* don't forget this to get a perfect centring */
}
<main>
<div class="row"></div>
</main>
<svg class="down-arrow" aria-hidden="true" viewBox="0 0 410.95 355.89">
<polygon fill="#000" points="205.47 354.89 410.08 0.5 0.87 0.5 205.47 354.89" /></svg>

Keep SVG proportions whilst reducing window (or container) size

I have an SVG graphic that is the child element of a container.
If I want it to fit the full width of this container I normally do preserveAspectRatio="none" and set the width to 100% (with an optional height value if I want to add some extra height to the SVG).
However, I would like to have it so the SVG fits the container, but when I drag the window to a smaller size the wedge shape (i.e. diagonal angle of the SVG), stays at the same angle, whilst also still filling the container.
In the sample code I've kept the preserveAspectRatio=none code to show the general effect I would like (the difference being of course I would like the angle of the wedge to stay the same as the window is reduced in size).
Is this possible? I'm struggling to think of a way to get this to work.
Here is a Codepen: https://codepen.io/emilychews/pen/pGbPby
body {
margin: 0;
padding: 0;
display: flex;
width: 100%;
height: 100vh;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
height: 10rem;
background: lightblue;
position: relative;
}
.wedge {
width: 100%;
left: 0;
bottom: 0;
height: 4rem;
position: absolute
}
<div class="container">
<svg class="wedge" preserveAspectRatio="none" aria-hidden="true" viewBox="0 0 376.9 122.7">
<polyline fill="#000" points="376.9 122.69 0 122.35 376.55 0 376.9 122.69"/>
</svg>
</div>
If you want the SVG to keep its aspect ratio, and not stretch, then don't use preserveAspectRatio="none". Use a different preserveAspectRatio value. For example "xMinYMax slice".
body {
margin: 0;
padding: 0;
display: flex;
width: 100%;
height: 100vh;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
height: 10rem;
background: lightblue;
position: relative;
}
.wedge {
width: 100%;
left: 0;
bottom: 0;
height: 4rem;
position: absolute
}
<div class="container">
<svg class="wedge" preserveAspectRatio="xMinYMax slice" aria-hidden="true" viewBox="0 0 376.9 122.7">
<polyline fill="#000" points="376.9 122.69 0 122.35 376.55 0 376.9 122.69"/>
</svg>
</div>
However you are obviously going to have to decide what you want to happen if the page gets wide enough that the wedge gets so tall that the top gets clipped off. For example in your example (and in my example above) you have limited the height of the SVG to 4em. So you are only seeing the bottom of the wedge.
If I change the SVG height to 100%, you see more of it.
body {
margin: 0;
padding: 0;
display: flex;
width: 100%;
height: 100vh;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
height: 10rem;
background: lightblue;
position: relative;
}
.wedge {
width: 100%;
left: 0;
bottom: 0;
height: 100%;
position: absolute
}
<div class="container">
<svg class="wedge" preserveAspectRatio="xMinYMax slice" aria-hidden="true" viewBox="0 0 376.9 122.7">
<polyline fill="#000" points="376.9 122.69 0 122.35 376.55 0 376.9 122.69"/>
</svg>
</div>
Of course, you may also want to reduce the angle of the wedge.

How do I scale my SVG to fit the size of its container div in d3?

Is there a way to make my SVG scale to the dimensions of #mapDiv?
HTML:
<div id="mapDiv">
<svg id="mapSVG" width="960" height="600" viewbox="0 0"></svg>
</div>
JS:
var svg = d3.select("#mapSVG"),
width = +svg.attr("width"),
height = +svg.attr("height");
CSS:
#mapDiv{
border: 2px solid #000;
border-radius: 7px;
float: left;
width: 50%;
}
#mapSVG{
display: block;
margin: auto;
}
I don't need the SVG to scale when the page is resized, by the way. As long as the SVG scales to the div size when the page loads, that's good enough for me.
If you're looking for a CSS solution, simply give your #mapDiv a fixed height and width, and then have the svg element take up 100% of both the width and height. This can be seen in the following:
#mapDiv {
height: 50px;
width: 50px;
}
#mapDiv > svg {
border: 5px solid black;
width: 100%;
height: 100%;
}
<div id="mapDiv">
<svg id="mapSVG" width="960" height="600" viewbox="0 0 960 600">
</svg>
</div>
Hope this helps! :)

Flexbox is using original SVG width instead of scaled width

I am trying to make a section which has 2 flex div boxes where:
Left one: Has the auto width of a img.
Right one: Has the left width.
I have almost everything working, but I am stuck with this:
It seems to set the original width of the SVG file, not the "scaled" one. The SVG image is contained in an img tag and scaled with height: 100%; and width: auto;.
I have been searching for multiple solutions in internet but none of them worked.
Is there any way to solve it? (without JavaScript if possible)
index.htm
<div id="SideMessage">
<div class="Decoration">
<img src="./image.svg" alt="ERROR">
</div>
<div class="Message">
<span>SAMPLE TEXT</span>
</div>
</div>
style.css
div#SideMessage {
/**/background: lightblue;
display: flex;
height: 64px;
left: 0;
position: absolute;
right: 0;
top: 0;
width: 100%;
}
div#SideMessage > div.Decoration {
/**/border: 1px dashed green;
flex-basis: auto;
flex-grow: 0;
flex-shrink: 0;
}
div#SideMessage > div.Message {
/**/border: 1px dashed red;
flex-basis: auto;
flex-grow: 1;
flex-shrink: 1;
}
div#SideMessage > div.Decoration > img {
height: 100%;
width: auto;
}
div#SideMessage > div.Message > span {
font-size: 20px;
letter-spacing: 2px;
}
image.svg
<svg xmlns="http://www.w3.org/2000/svg" width="18.475px" height="32px" viewBox="0 0 18.475 32">
<polygon fill="rgb(0, 0, 0)" points="0,0 18.475,0 0,32"/>
</svg>
2 years later but unwrapping the <img> from <div class="Decoration"> should fix it. And add box-sizing: border-box; to prevent overflow from its border/padding.

centering an SVG inside a div

I want to display an SVG image inside a position:fixed div. I have
<div class="main">
<svg class="svg" viewBox="0 0 180 100">
<rect height="100%" width="100%" fill="#003300"></rect>
</svg>
</div>
and style
.main {
position:fixed;
left: 100px;
height: 100%;
width:100%;
background: #33AAAA;
}
.svg {
display: block;
height: 100%;
width: 100%;
position:static;
}
I want to center the SVG horizontally and vertically. I get a strange behavior. Changing the size of the browser window, shows that when the svg is smaller than available width, it is weirdly placed. For example: there is more space on the left than on the right.
Codepen (including CSS reset): Codepen
You are setting the width to 100%, then shifting it over by 100px. The width is still calculated as whatever the 100% width is. To center it the way you want, you will need to subtract 100px from the width or nest things differently.
.main {
position:fixed;
left:100px;
height: 100%;
width:calc(100% - 100px);
background: #33AAAA;
}
It looks like its because you have the left: 100px in your main class. If you take that out it centers correctly.
here is the main class that should work:
.main {
position:fixed;
left:100px;
height: 100%;
right: 0;
top:0;
bottom:0;
background: #33AAAA;
}