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?
I was playing around with <iframe>
, since it has its own window
interface.
What I did here:
.matchMedia()
methodIt 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);
}