Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to listen to Container Style Query change

Update 2 - Added a JS BIN: https://jsbin.com/wujizuyuqi/edit?html,console,output

<!doctype html>
<html lang="en" data-bs-theme="auto">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="DublinDev">

    <title>Title</title>

    <style>
        .parent {
            container-type: size;
        }

        @container (min-width: 500px) {
            .child {
                --size: "Large";
                background-color: olive;
            }
        }

        @container (max-width: 499px) {
            div .child {
                --size: "Small";
                background-color: cyan;
            }
        }
    </style>
</head>

<body>
    <div id="parent" class="parent" style="width: 100%; height: 200px; border: 1px solid #ccc;">
        <div id="child" style="width: 100%; height: 100%;" class="child"></div>
    </div>

    <script>
        const parent = document.getElementById("parent");
        const child = document.getElementById("child");

        const resizeObserver = new ResizeObserver((entries) => {
            var size = window.getComputedStyle(child).getPropertyValue("--size")
            console.log(`Size: ${size}`);
        });

        resizeObserver.observe(child);
    </script>
</body>

</html>

Note, the whole point is to try getting rid of the resize observer and rely on an event, the same way as it's done for the regular media quires.

Update 1 - I found a horrible workaround based on a custom property + ResizeObserver + window.getComputedStyle(), it might hold the water until a better solution arrives. It'd be still be great to know if the listener is coming.

Questions: How do I add a listener for a container style query change by analogy with media queries?


1 Answers

I was playing around with <iframe>, since it has its own window interface.
What I did here:

  1. create a "dummy" iframe
  2. set the iframe size to the exact same size as the desired element
  3. get a MediaQueryList using .matchMedia() method

It still needs a ResizeObserver (or a eventListener on resize event) to update the iframe size according to the element size, when it changes. And I don't think you can't get rid of any sort of observer until there's a native implementation like element.matchMedia();
It's a bit of a hacky wacky solution, but you will get the same MediaQueryList as if you would run window.matchMedia() and you can work the same way with it. .matches, .onchange, .addEventListener() ..

Here's a JSFiddle because SO doesn't like iframes. Also a note, in JSFiddle and JS Bin the eventListener doesn't work for the MediaQueryList coming from an iframe. So instad, I recommend to create a simple .html file and open it in your browser.
https://jsfiddle.net/sca5kbgh/55/

// HTML

<div id="parent" class="parent" style="width: 100%; height: 200px; border: 1px solid #ccc;">
    <div id="child" style="width: 100%; height: 100%;" class="child"></div>
</div>

// CSS

.parent {
  container-type: size;
}

@container (min-width: 500px) {
  .child {
    background-color: olive;
  }
}

@container (max-width: 499px) {
  div .child {
    background-color: cyan;
  }
}

// JS

class ContainerMatchMedia {
    constructor (element, containerQueryString) {
    this.element = element;
    this.containerQueryString = containerQueryString;
    
    this.setupIframe();
    this.setupObserver();
    this.matchMedia();
  }
  
  setupIframe() {
    // Create an iframe, we use it to "fake" the element size annd apply matchmedia
    this.iframe = document.createElement('iframe')
    this.iframe.id = 'container-match-media';

    // some stylings to hide the iframe
    this.iframe.style.position = 'absolute';
    this.iframe.style.top = 0;
    this.iframe.style.left = 0;
    this.iframe.style.display = 'block';
    this.iframe.style.transform = 'translate(-100vw, -100vh)';
    
    // append the iframe to root element
    document.documentElement.appendChild(this.iframe);
  }
  
  setupObserver() {
    // we still need an observer to change the iframe size when the element changes
    this.resizeObserver = new ResizeObserver((entries) => {
      this.resizeIframeToElement(this.element, this.iframe);
    });

    this.resizeObserver.observe(this.element);  
  }
  
  resizeIframeToElement() {
    // get the elements dimensions
    var elementRect = this.element.getBoundingClientRect();

    // set the iframe dimensions to same as elements dimensions
    this.iframe.width = elementRect.width;
    this.iframe.height = elementRect.height;
    
    // here we see the MediaQueryList is changing when the iframe is being resized
    if (this.MediaQueryList) {
        console.log('Matches: ', this.MediaQueryList.matches);
    };
  }
  
  matchMedia() {
    /**
     * Now we have an iframe with same dimensions as the element
     * matchMedia will return a MediaQueryList
     */
    this.MediaQueryList = this.iframe.contentWindow.matchMedia(this.containerQueryString);
  }
}


const child = document.getElementById("child");
const CMM = new ContainerMatchMedia(child, '(min-width: 500px)');

// unfortunately, .onchange (or .addEventListener('change' ...)) doesn't work
CMM.MediaQueryList.onchange = (event) => {
    console.log(event);
}
like image 119
Mugentoki Avatar answered Dec 11 '25 20:12

Mugentoki



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!