SVG circle should adapt to CSS grid height without overlapping
Asked Answered
R

2

10

In the following example, the circle adapts to the grid width and keeps its aspect ratio. As the container width shrinks, the circles shrinks with it...

When height is smaller than width however (second box), the circle doesn't shrink overlaps outside the grid instead Is there a way to have it adapt to the height too while keeping the aspect ratio?

.container {
	display: grid;
	background-color: greenyellow;
	margin: 5px;
	min-height: 10px;
	min-width: 10px;
}

.portrait {
		max-height: 100px;
		max-width: 200px;
}

.landscape {
		max-height: 200px;
		max-width: 100px;
}

.aspect-ratio {
	grid-column: 1;
	grid-row: 1;
	background-color: deeppink;
	border-radius: 50%;
 align-self: center;
	justify-self: center;
}
<div class="container landscape">
  <svg class="aspect-ratio" viewBox="0 0 1 1"></svg>
</div>

<div class="container portrait">
  <svg class="aspect-ratio" viewBox="0 0 1 1"></svg>
</div>

The result should look like this: enter image description here

Rolph answered 9/1, 2020 at 14:26 Comment(1)
Do you have the possibility to edit the svg code?Reneerenegade
P
9

After fiddling around some time with this problem I figured this is not a problem with the grid layout.

Since you never define height/max-height or width/max-width for your SVG, what is being computed is width:auto and height:auto

Note: I bring up max-height/max-width since these would have priority over the width/height values.


Now then, why does your landscape layout work and your portrait layout doesn't work? Because width is "taking priority" over your height.

And that's why, in block layout, an auto height is based on the total height of descendants and an auto width is based on the containing block width.

Source - Why does width:auto behave differently than height:auto?

This means your SVG is taking the width from its container and the height from its descendants.

In your example, your SVG has no descendants, but has a parent with a defined max-width. But what does this mean for the SVG's height?

The SVG is calculating it's height through the intrisic ratio:

If the ‘viewBox’ on the ‘svg’ element is correctly specified:

  1. let viewbox be the viewbox defined by the ‘viewBox’ attribute on the ‘svg’ element
  2. return viewbox.width / viewbox.height

Source

And your intrisic ratio is 1/1 = 1

So you're forcing your height=width


The solution(s)

The solution is setting a width or a margin to your SVG depending on the proportion of your parent div.

In the example you gave, you have a proportion of 2:1 so your SVG should have width:50% or margin: 0 25%.

This means that if you want to set a different proportion, you must adjust accordingly (see portrait-1-3 in the code bellow).

Setting a CSS rule for each proportion could be avoided if height is not a constraint, since you could define a width for your SVG and container, letting the elements adjust the height to keep your aspect ratio.

I've also added the a final example if someone doesn't care about keeping the aspect ratio but needs to contain the SVG in a set height and width container.

.container {
  display: grid;
  background-color: greenyellow;
  margin: 5px;
  min-height: 10px;
  min-width: 10px;
}

.portrait {
  max-height: 100px;
  max-width: 200px;
}


/* Add 25% margin left and right to center the image  OR set the image width to 50% */

.portrait>.aspect-ratio {
  margin: 0 25%;
  /*
    or 
    
    width:50%;
  */
}

.landscape {
  max-height: 200px;
  max-width: 100px;
}

.aspect-ratio {
  grid-column: 1;
  grid-row: 1;
  background-color: deeppink;
  border-radius: 50%;
  align-self: center;
  justify-self: center;
}


/* Set fixed max-height and max-width rule (33% proportion)  */

.portrait-1-3 {
  max-height: 100px;
  max-width: 300px;
}


/* Align image with the new proportion */

.portrait-1-3>.aspect-ratio {
  margin: 0 33.33%;
  /*
    or 
    
    width:33.3%;
  */
}


/* Removed max-height and let the container adjust the height according to the max-width rule and the proportion set  */

.portrait-any-height {
  max-width: 400px;
}


/* Height will be adjusted through the width/margin defined here, so 10% width will correspond to 40px of height (10%*400px)*(aspect ratio=1)*/

.portrait-any-height>.aspect-ratio {
   width:10%;
}


/* Set fixed max-height and max-width rule (proportion doesn't matter)  */

.portrait-squeezed {
  max-height: 100px;
  max-width: 300px;
}


/* Make sure SVG complies with the the given max with and height (squeezing your svg)  */

.portrait-squeezed>.aspect-ratio {
  max-height: inherit;
  max-width: inherit;
}
<div class="container landscape">
  <svg class="aspect-ratio" viewBox="0 0 1 1"></svg>
</div>

<div class="container portrait">
  <svg class="aspect-ratio" viewBox="0 0 1 1"></svg>
</div>

<div class="container portrait-1-3">
  <svg class="aspect-ratio" viewBox="0 0 1 1"></svg>
</div>

<div class="container portrait-any-height">
  <svg class="aspect-ratio" viewBox="0 0 1 1"></svg>
</div>

<div class="container portrait-squeezed">
  <svg class="aspect-ratio" viewBox="0 0 1 1"></svg>
</div>

Do note I'm not very proficient working with SVG's and this response is a result of research and a lot of experimentation! I'd be happy to receive any adjustments and/or corrections to my response.

Pensive answered 16/1, 2020 at 17:52 Comment(8)
Thanks for the detailed explanation... I must notice that your research unfortunately returned the same results as mine. There seem to be no solution for my requirements: 1. SVG aspect ratio is fixed. 2. container aspect ratio is variable / unknown 3. container size is limited by both height and width depending on environment (e.g. browser window size) The proposed solution by setting the SVG width to a certain percentage of the container, would work if the container had fixed aspect ratio, but it doesn't.Rolph
Giving up max-height also doesn't work, that would just mean moving the overflow issue to the parentRolph
With those 3 requirements it doesn't seem like there's a solution, that is, using only CSS. Is using Javascript to calculate the apropriate SVG width out of question?Pensive
That is what my current solution is doing which I would like to avoid ;)Rolph
I'm hoping that aspect-ratio will help solve this, but I'm not even sure about that. drafts.csswg.org/css-sizing-4/#aspect-ratioRolph
That's too bad. :/ I have another suggestion, although I myself don't like it very much. If you only need to adjust the container's width/height according to the view port you could use @media rules setting different container/svg width's and height's for each different view port size. The problem is you'd probably need a very big amount of rules.Pensive
You're talking about the aspect-ratio rule? Be careful, there is a big banner on the page saying "This spec is not yet ready for implementation. It exists in this repository to record the ideas and promote discussion." I just tried it in chrome and it didn't seem to recognize the rule!Pensive
Media queries are also a no go because I don't control where my component is used, so I don't know the viewport ratio needed. Element queries would work, but that too is not there yet. Yeah, I know aspect-ratio is not ready yet. I just meant that this might be the solution in the future. But I'm not sure yet from the spec it wouldn't have the exact same issueRolph
R
0

Add to the container specific height & width alongside max-height & max-width.

vw & vh units can be used to make this layout more flexible.

  max-height: 100px;
  max-width: 200px;
  width: 100vw;
  height: 100vh;

This way it is possible to set max-width: 100%; max-height: 100%; to the svg and it will not overlap.


-EDIT-

Since the svg viewbox is already rendered with the correct aspect ratio, it is safe to move the svg background inside the svg itself to have the expected result: a cicle tag will do the trick.

Is it possible to imitate the border-radius with another circle tag within a mask

All the other elements of the svg then should be placed inside a masked group. ie: <g mask="url(#mask)">

.container {
  display: grid;
  background-color: greenyellow;
  margin: 5px;
  min-height: 10px;
  min-width: 10px;
}

.portrait {
  max-height: 100px;
  max-width: 200px;
  width: 100vw;
  height: 100vh;
}

.landscape {
  max-height: 200px;
  max-width: 100px;
  width: 100vw;
  height: 100vh;
}

.aspect-ratio {
  grid-column: 1;
  grid-row: 1;
  align-self: center;
  justify-self: center;
  max-width: 100%;
  max-height: 100%;
}

.aspect-ratio .bkg{
  fill: deeppink;
}
<div class="container landscape">
  <svg class="aspect-ratio" viewBox="0 0 1 1">
    <mask id="mask">
      <circle cx=".5" cy=".5" r=".5" fill="#fff"/>
    </mask>
    <circle cx=".5" cy=".5" r=".5" class="bkg"/>
    <g  mask="url(#mask)">

    </g>
  </svg>
</div>

<div class="container portrait">
  <svg class="aspect-ratio"viewBox="0 0 1 1">
    <mask id="mask">
      <circle cx=".5" cy=".5" r=".5" fill="#fff"/>
    </mask>
    <circle cx=".5" cy=".5" r=".5" class="bkg"/>
    <g  mask="url(#mask)">

    </g>
  </svg>
</div>
Reneerenegade answered 21/1, 2020 at 13:26 Comment(9)
The question specified " Is there a way to have it adapt to the height too while keeping the aspect ratio" which your example does not. It is a way to force the SVG inside the container but it will not produce the intended result. It's the same as the last example of my response where the svg is squeezed to fit the containerPensive
The explanation may be a little confused but with the solution suggested the svg height adapts to the height of the container.Reneerenegade
But it does not keep the SVG aspect-ratio! Have you seen the desired result that the OP posted?Pensive
Oh, wow... I just checked it with Chrome and I now understand what are you saying. I coded it on Firefox and with that browser it works as intended, I didn't expect the result was so different. My badReneerenegade
That's actually pretty interesting behaviour, I'm guessing Firefox and Chrome compute differently for 100%? Anyway, we all make mistakes, otherwise SO wouldn't exist :DPensive
I updated the answer to make it crossbrowser. This solution needs a different markup but doesn't works only on Firefox.Reneerenegade
@Reneerenegade you might be onto something with your first solution?! The result in Firefox is indeed almost what is expected. Why almost, because in this solution, the container's height is no longer dynamic. So instead of the circle overflowing the container, it's the container that is overflowing its parent and no longer being resized. I also opened a ticket with webcompat to follow up on the difference between the browsers: webcompat.com/issues/47789Rolph
Regarding the second solution with the mask, while that might indeed work. That would mean that the SVG is always filled with plain color. While that is the case in this example, in real code I also have circles with 'content' which would be cut of by the maskRolph
I added the mask in the svg to replicate the border-radius effect in your code: that property will clip your content anywayReneerenegade

© 2022 - 2024 — McMap. All rights reserved.