Can media queries resize based on a div element instead of the screen?
Asked Answered
N

12

503

I would like to use media queries to resize elements based on the size of a div element they are in. I cannot use the screen size as the div is just used like a widget within the webpage, and its size can vary.

Newton answered 3/9, 2012 at 17:2 Comment(4)
How can the size of your widget vary? Is your page fixed width (960px by example) with viewports above 1024px or are both fluid? That's unclear and would be useful to knowTetartohedral
The widget size can vary as if we are shown within a facebook app we show different widgets than if we are on a normal webpageNewton
THis works pretty well and is a relatively small build (~4kb minified) github.com/tysonmatanich/elementQueryAliphatic
See Container Queries, an example is shown in this SO answer: https://mcmap.net/q/75258/-container-query-collapses-width-of-elementQuixotism
B
378

Yes, CSS Container Queries are what you're looking for. The CSS Containment Module is the specification that details this feature. You can check browser support here.

You can read more about the decade of work, including proposals, proofs-of-concept, discussions and other contributions by the broader web developer community here! For more details on how such a feature might work and be used, check out Miriam Suzanne's extensive explainer.


Media queries aren't designed to work based on elements in a page. They are designed to work based on devices or media types (hence why they are called media queries). width, height, and other dimension-based media features all refer to the dimensions of either the viewport or the device's screen in screen-based media. They cannot be used to refer to a certain element on a page.

If you need to apply styles depending on the size of a certain div element on your page, you'll have to use JavaScript to observe changes in the size of that div element instead of media queries.

Alternatively, with more modern layout techniques introduced since the original publication of this answer such as flexbox and standards such as custom properties, you may not need media or element queries after all. Djave provides an example.

Billposter answered 7/9, 2012 at 13:39 Comment(3)
I heard about some way I may be able to use functions with media queries to get the size from a containing element. Is there some way I can combine this with media queries to resize elements?Newton
You'd have to use JS to periodically get the width of your element and react accordingly (periodically = every second or so or it'll slow some browsers ; accordingly = by toggling classes styling your widget if you want the fastest method).Tetartohedral
To accomplish this natively, we'd need @element queries. W3C has some good documentation on the rhyme/reason for @media queries: w3.org/TR/css3-mediaqueries/#width (this link takes you to the section discussing widths --- widths of the media type, not the elements contained within)Columbia
L
123

I've just created a javascript shim to achieve this goal. Take a look if you want, it's a proof-of-concept, but take care: it's a early version and still needs some work.

https://github.com/marcj/css-element-queries

Lath answered 2/6, 2013 at 3:29 Comment(0)
B
58

Container queries demo

First, set up a grid.

<div class="grid">

  <div class="post">
    <div class="post--content">
      <h2>Lorem, ipsum.</h2>
      <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Unde, sequi!</p>
      <button>Lorem.</button>
    </div>
  </div>

  <div class="post">
    <div class="post--content">
      <h2>Pariatur, doloremque.</h2>
      <p>Suscipit, ex porro delectus officia saepe facere nisi odio architecto.</p>
      <button>Animi!</button>
    </div>
  </div>

  ... more posts
</div>

Use CSS to give certain of the same elements different sizes

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
}

.post {
  container-type: inline-size;
  min-width: 0; /* important so the grid can collapse */
}
.post:nth-child(1) {
  grid-column: span 7;
}
.post:nth-child(2) {
  grid-column: span 5;
}
.post:nth-child(3) {
  grid-column: span 8;
}
...

Gotcha #1

Make sure you apply

.post {
  container-type: inline-size;
}

To what will be your container.

Gotcha #2

You cannot change the .post div with a container query in this example. You can change the content inside it as this container changes size.

Now, you should be ready to mess around:

@container (min-width: 250px) {
  .post--content {
    background: hotpink;
  }
}
@container (min-width: 350px) {
  .post--content {
    background: blue;
  }
}

Full demo:

* {
  box-sizing: border-box;
}

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
}

.post {
  container-type: inline-size;
  min-width: 0;
}
.post:nth-child(1) {
  grid-column: span 7;
}
.post:nth-child(2) {
  grid-column: span 5;
}
.post:nth-child(3) {
  grid-column: span 8;
}
.post:nth-child(4) {
  grid-column: span 4;
}
.post:nth-child(5) {
  grid-column: span 3;
}
.post:nth-child(6) {
  grid-column: span 9;
}
.post:nth-child(7) {
  grid-column: span 10;
}

.post--content {
  overflow: hidden;
  padding: 20px;
  height: 100%;
}

@container (min-width: 250px) {
  .post--content {
    background: hotpink;
  }
}
@container (min-width: 350px) {
  .post--content {
    background: blue;
  }
}
@container (min-width: 450px) {
  .post--content {
    background: red;
    text-align: right;
  }
  
  .post--content button{
    background: green;
    color: #fff;
    font-weight: bold;
    border: none;
  }
}
@container (min-width: 550px) {
  .post--content {
    background: orange;
    text-align: right;
    font-family: sans-serif;
  }
  .post--content p{
    font-size: 1.4em;
  }
}
@container (min-width: 650px) {
  .post--content {
    background: lime;
    text-align: right;
  }
  
  .post--content button{
    border-radius: 10px;
    padding: 20px;
  }
}
<div class="grid">

  <div class="post">
    <div class="post--content">
      <h2>Lorem, ipsum.</h2>
      <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Unde, sequi!</p>
      <button>Lorem.</button>
    </div>
  </div>

  <div class="post">
    <div class="post--content">
      <h2>Pariatur, doloremque.</h2>
      <p>Suscipit, ex porro delectus officia saepe facere nisi odio architecto.</p>
      <button>Animi!</button>
    </div>
  </div>

  <div class="post">
    <div class="post--content">
      <h2>Voluptates, est.</h2>
      <p>Est doloremque, culpa illo praesentium laboriosam omnis dolores ducimus veniam!</p>
      <button>Distinctio.</button>
    </div>
  </div>

  <div class="post">
    <div class="post--content">
      <h2>Ea, temporibus.</h2>
      <p>Quibusdam doloremque ipsa, laudantium ad in doloribus hic? Commodi, libero?</p>
      <button>Culpa?</button>
    </div>
  </div>

  <div class="post">
    <div class="post--content">
      <h2>Optio, nesciunt.</h2>
      <p>A laborum facilis ex autem tenetur magnam officia aliquam aut!</p>
      <button>Illum.</button>
    </div>
  </div>
  
  <div class="post">
    <div class="post--content">
      <h2>Optio, nesciunt.</h2>
      <p>A laborum facilis ex autem tenetur magnam officia aliquam aut!</p>
      <button>Illum.</button>
    </div>
  </div>
  
  <div class="post">
    <div class="post--content">
      <h2>Optio, nesciunt.</h2>
      <p>A laborum facilis ex autem tenetur magnam officia aliquam aut!</p>
      <button>Illum.</button>
    </div>
  </div>
  
  <div class="post">
    <div class="post--content">
      <h2>Optio, nesciunt.</h2>
      <p>A laborum facilis ex autem tenetur magnam officia aliquam aut!</p>
      <button>Illum.</button>
    </div>
  </div>
  
  <div class="post">
    <div class="post--content">
      <h2>Optio, nesciunt.</h2>
      <p>A laborum facilis ex autem tenetur magnam officia aliquam aut!</p>
      <button>Illum.</button>
    </div>
  </div>
</div>

Reference:

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries

and there is a polyfill to get browsers that don't yet support it up to speed:

https://github.com/GoogleChromeLabs/container-query-polyfill

There is a nice little overview video of it here:

https://www.youtube.com/watch?v=gCNMyYr7F6w

This has now shipped to Chrome (05 September 2022)

https://caniuse.com/css-container-queries

Original answer

From a layout perspective, it is possible using modern techniques.

Its made up (I believe) by Heydon Pickering. He details the process here: http://www.heydonworks.com/article/the-flexbox-holy-albatross

Chris Coyier picks it up and works through a demo of it here: https://css-tricks.com/putting-the-flexbox-albatross-to-real-use/

To restate the issue, below we see 3 of the same component, each made up of three orange divs labelled a, b and c.

The second two's blocks display vertically, because they are limited on horizontal room, while the top components 3 blocks are laid out horizontally.

It uses the flex-basis CSS property and CSS Variables to create this effect.

Final example

.panel{
  display: flex;
  flex-wrap: wrap;
  border: 1px solid #f00;
  $breakpoint: 600px;
  --multiplier: calc( #{$breakpoint} - 100%);
  .element{
    min-width: 33%;
    max-width: 100%;
    flex-grow: 1;
    flex-basis: calc( var(--multiplier) * 999 );
  }
}

Demo

Heydon's article is 1000 words explaining it in detail, and I'd highly recommend reading it.

Begum answered 26/9, 2019 at 13:49 Comment(0)
D
41

A Media Query inside of an iframe can function as an element query. I've successfully implement this. The idea came from a recent post about Responsive Ads by Zurb. No Javascript!

Dethrone answered 16/7, 2014 at 19:25 Comment(0)
T
30

This is currently not possible with CSS alone as @BoltClock wrote in the accepted answer, but you can work around that by using JavaScript.

I created a container query (aka element query) polyfill to solve this kind of issue. It works a bit different than other scripts, so you don’t have to edit the HTML code of your elements. All you have to do is include the script and use it in your CSS like so:

.element:container(width > 99px) {
    /* If its container is at least 100px wide */
}

https://github.com/ausi/cq-prolyfill

Togliatti answered 5/10, 2015 at 13:35 Comment(0)
W
18

I ran into the same problem a couple of years ago and funded the development of a plugin to help me in my work. I've released the plugin as open-source so others can benefit from it as well, and you can grab it on Github: https://github.com/eqcss/eqcss

There are a few ways we could apply different responsive styles based on what we can know about an element on the page. Here are a few element queries that the EQCSS plugin will let you write in CSS:

@element 'div' and (condition) {
  $this {
    /* Do something to the 'div' that meets the condition */
  }
  .other {
    /* Also apply this CSS to .other when 'div' meets this condition */
  }
}

So what conditions are supported for responsive styles with EQCSS?

Weight Queries

  • min-width in px
  • min-width in %
  • max-width in px
  • max-width in %

Height Queries

  • min-height in px
  • min-height in %
  • max-height in px
  • max-height in %

Count Queries

  • min-characters
  • max-characters
  • min-lines
  • max-lines
  • min-children
  • max-children

Special Selectors

Inside EQCSS element queries you can also use three special selectors that allow you to more specifically apply your styles:

  • $this (the element(s) matching the query)
  • $parent (the parent element(s) of the element(s) matching the query)
  • $root (the root element of the document, <html>)

Element queries allow you to compose your layout out of individually responsive design modules, each with a bit of 'self-awareness' of how they are being displayed on the page.

With EQCSS you can design one widget to look good from 150px wide all the way up to 1000px wide, then you can confidently drop that widget into any sidebar in any page using any template (on any site) and

Wise answered 12/11, 2015 at 19:26 Comment(0)
B
8

The question is very vague. As BoltClock says, media queries only know the dimensions of the device. However, you can use media queries in combination with descender selectors to perform adjustments.

.wide_container { width: 50em }

.narrow_container { width: 20em }

.my_element { border: 1px solid }

@media (max-width: 30em) {
    .wide_container .my_element {
        color: blue;
    }

    .narrow_container .my_element {
        color: red;
    }
}

@media (max-width: 50em) {
    .wide_container .my_element {
        color: orange;
    }

    .narrow_container .my_element {
        color: green;
    }
}

The only other solution requires JS.

Baumgardner answered 7/9, 2012 at 17:49 Comment(0)
C
4

You can use the ResizeObserver API. It's still in it's early days so it's not supported by all browsers yet (but there's several polyfills that can help you with that).

This API allows you to attach an event listener when resizing a DOM element.

Demo 1 - Demo 2

Cellarage answered 23/10, 2019 at 10:55 Comment(2)
There is a polyfill for the draft specification. github.com/juggle/resize-observerLandfall
Heydon's article provides an example implementation of this. Search "ResizeObserver" on the page: heydonworks.com/article/the-flexbox-holy-albatrossGesualdo
C
3

The only way I can think that you can accomplish what you want purely with css, is to use a fluid container for your widget. If your container's width is a percentage of the screen then you can use media queries to style depending on your container's width, as you will now know for each screen's dimensions what is your container's dimensions. For example, let's say you decide to make your container's 50% of the screen width. Then for a screen width of 1200px you know that your container is 600px

.myContainer {
  width: 50%;
}

/* you know know that your container is 600px 
 * so you style accordingly
*/
@media (max-width: 1200px) { 
  /* your css for 600px container */
}
Collencollenchyma answered 17/6, 2015 at 9:5 Comment(1)
No, it looks ugly on a screen. Screen size changes faster, but element appears on a screen later and smaller.Lundell
M
3

I was also thinking of media queries, but then I found this:

Just create a wrapper <div> with a percentage value for padding-bottom, like this:

div {
  width: 100%;
  padding-bottom: 75%;
  background:gold; /** <-- For the demo **/
}
<div></div>

It will result in a <div> with height equal to 75% of the width of its container (a 4:3 aspect ratio).

This technique can also be coupled with media queries and a bit of ad hoc knowledge about page layout for even more finer-grained control.

It's enough for my needs. Which might be enough for your needs too.

Moue answered 30/7, 2017 at 23:46 Comment(0)
M
1

I found an easy way to explain it, @Djave did mention something about this type of query, but it was hard to understand... i eventually found the following solution: say we have the following HTML:

div {
    width: 50%;
    container-type: inline-size;
}
span {
    color: red;
}
@container (inline-size > 150px) {
    span {
        color: green;
    }        
}
<div>
    <span>Text</span>
</div>
<div>
    <span>Text</span>
</div>

If we have a div container and a span inside, and you want to change the span when the div is greater or smaller in width you give the div (container) a css attribute of container-type: inline-size; then you add the media query. the full CSS looks like this:

div {
    width: 50%;
    container-type: inline-size;
}
span {
    color: red;
}
@container (inline-size > 150px) {
    span {
        color: green;
    }        
}
Micropaleontology answered 29/6, 2023 at 9:18 Comment(0)
F
0

For mine I did it by setting the div's max width, hence for small widget won't get affected and the large widget is resized due to the max-width style.

  // assuming your widget class is "widget"
  .widget {
    max-width: 100%;
    height: auto;
  }
Flunkey answered 24/1, 2018 at 8:28 Comment(1)
The question mentioned that the widget size might vary, setting max-width will not change the size of the widget when the container is wide. .widget is just a sample as the class for the widget's div.Flunkey

© 2022 - 2024 — McMap. All rights reserved.