Zze
Zze

Reputation: 18865

img width relative to containing div

Original Question

What is the most efficient way to calculate the width of an image relative to it's containing div in css?

I have the following snippet which sets #image1.width to a percentage that is relative to the width of its parent. I'm using a percentage because I need the image to scale proportionately to the parent when the screen is resized.

div {
  position: relative;
  display: block;
  width: 100%;
}

#image1 {
  position: absolute;
  left: 10%;
  top: 10%;
  width: 29.43%;
  height: auto;
}

#under {
  width: 100%;
}
<div>
  <img id="image1" src="http://placehold.it/206x115">
  <img id="under" src="http://placehold.it/700x300/ff00f0/ffffff">
</div>

It is currently working as intended, except that I have to manually calculate the width percentage for every single image. i.e.

#image1 dimensions == 206x115
#under dimensions == 700x300
new #image1.width % == #image1.width / #under.width == 206/700 == ~29.43%

What I want to know is if there id a calc() method or similar I can implement to ease/streamline this process?

I was going to use width: calc(100% / 700) however this obviously will not work when the screen size changes.



Goals

To re-iterate, it is imperative that the #under image scales with the screen size and the #image remains proportionate.

I want the natural image ratios preserved with one another (i.e. an image that is one quarter the size of the other will remain as such at all browser widths).

Note: The html can be reconfigured in any way to achieve this.

Target browsers: Chrome, Firefox, Edge.



Post Bounty Review

Comment on @Obsidian Age's answer (end of first bounty 31.03.17):

Unfortunately @Obsidian Age's answer is not correct - it is close but not quite and I just wanted to clarify this here... Below is a snippet from his answer... Note that I think it is a good answer, just clarifying why it has not been accepted:

:root {
  --width: 90vw; // Must be viewport-driven
}

#image1 {
  width: calc(var(--width) / 3); // The 3 can be replaced with any float
}

Setting --width: 90vw what happens if body or div have a max-width set? This is also very hard to calculate for all devices when factoring in viewport-scaling.

#image1 { width:calc(var(--width) / 3); } This equates to calc(90vw / 3) which is 30vw which would equate to 30% of the images width. But how do we work out the number to divide by? Well it's back to where we started... width:calc(var(--width) * calc(206/700*100)); And this is why I have not accepted this answer.

Upvotes: 32

Views: 7325

Answers (5)

Just a student
Just a student

Reputation: 11050

Inspired by the answer of Robby Cornelissen, here is an approach that works in the targeted browsers currently. Its only drawback is that the dimensions of the images have to be specified in the HTML (well, the SVG really) explicitly.

Here is a demo.

<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="100%" viewBox="0 0 700 300">
     
  <image x="0" y="0" width="700" height="300"
      xlink:href="http://placehold.it/700x300/ff00f0/ffffff"/>
  <image x="10%" y="10%" width="206" height="115"
      xlink:href="http://placehold.it/206x115"/>
</svg>


The approach, like Robby's answer, uses the SVG image element. That currently defaults to having zero width and height when no dimensions are specified explicitly, but will change in SVG 2. This means that as soon as SVG 2 is supported by browsers, instead of specifying the image dimensions, we could set width="auto" height="auto" and be done.

The value auto for width and height on the ‘image’ element is calculated from the referenced image's intrinsic dimensions and aspect ratio, according to the CSS default sizing algorithm.
SVG 2 Editor's Draft, § 7.8. Sizing properties: the effect of the ‘width’ and ‘height’ properties. Accessed 2017-12-18.

Upvotes: 1

andreifin
andreifin

Reputation: 41

One thing that might work, if you are willing to use bootstrap (https://getbootstrap.com/examples/grid/):

<!-- parent -->
<div class="row">
  <div class="col-md-6">
    <img src="">
    <!-- this is 50% of the screen for min 992px, a full line otherwise -->
  </div>
  <div class="col-md-3">
    <img src="">
    <!-- this is 25% of the screen for min 992px, a full line otherwise-->
  </div>

  <div class=col-md-3>
    <img src="">
    <!-- this is 25% of the screen for min 992px, a full line otherwise -->
  </div>
</div>

You can group these as you want/need, just remember that the numbers have to add to 12 (in my example, 6+3+3). You can achieve a 100% width effect using col-md-12. Also, the md infix is just one of several options of cutoff between putting everything on the same line and stacking elements. You can check out http://getbootstrap.com/css/#grid for more details, as well as a couple of examples.

Upvotes: 2

demonofthemist
demonofthemist

Reputation: 4199

The best approach I think would be to eliminate width & use scale to fit the div, but the problem is scale transform do not accept any unit value, such as % or px or vw/vh!

.common_img_class{
    transform: scale(calc(100vw/700)); /* this won't work! */
    /* Idea here is to let image take is original width & then scale with respact to base image scaling. Base image scaling is detenined by *window_width/base_image _width*, here base image width is 700 as per you example */
}

Thus second best I can think of is to eliminate manual calculation of percent. Use calc() function to do that for you.

#firsrt_img{
    width: calc((206/700) * 100%);
}
#second_img{
    width: calc((306/700) * 100%);
}

Here you still have to write width for all but atleast spared from percent or ratio calculation.

Note:
If anyone can help with first approach, their inputs are welcome.

Upvotes: 5

Robby Cornelissen
Robby Cornelissen

Reputation: 97381

I realize that the question prompts for a pure CSS solution, but I liberally interpreted that as meaning "no JavaScript".

In that vein, here's a solution using an embedded SVG:

<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="100%" viewBox="0 0 700 300">
     
  <image x="0" y="0"
      xlink:href="http://placehold.it/700x300/ff00f0/ffffff"/>
  <image x="10%" y="10%"
      xlink:href="http://placehold.it/206x115"/>
</svg>

Upvotes: 6

Obsidian Age
Obsidian Age

Reputation: 42384

Unfortunately, CSS has no parent selector. While you can't make an element relative to the parent directly with CSS, what you can do with pure CSS is set a variable that both elements make use of:

:root {
--width: 90vw; // Must be viewport-driven
}

Now you can use this variable as both the (fixed) width of the parent element, and the calculation-driven width of the child:

#under {
  width: var(--width);
}

#image1 {
  width: calc(var(--width) / 3); // The 3 can be replaced with any float
}

Note that the variable must either be a fixed unit, or be relative to the viewport. If it were percentage-based, both #under and #image1 would base their width off of their respective parents. In order to have this work responsively, it must be based off of the viewport.

:root {
--width: 90vw;
}

div {
  position: relative;
  display: block;
  width: 100%;
}

#image1 {
  position: absolute;
  left: 10%;
  top: 10%;
  width: calc(var(--width) / 3);
  height: auto;
}

#image2 {
  position: absolute;
  left: 25%;
  top: 10%;
  width: 10%;
  height: auto;
}

#under {
  width: var(--width);
}
<div>
  <img id="image1" src="http://placehold.it/206x115">
  <img id="under" src="http://placehold.it/700x300/ff00f0/ffffff">
</div>

I've also created a JSFiddle of this here, where you can see both elements scale when the viewport resizes.

Hope this helps! :)

Upvotes: 11

Related Questions