Reputation: 5001
I have been unable to determine from any explanation I could find, about when the value of a css variable has been set., so I conducted a test
The html
<div class="flex">
<div class="item1">
<div class="included"></div>
</div>
<div class="item2">
<div class="included"></div>
</div>
</div>
the css
:host {
display: grid;
place-content: center;
}
.flex {
display: flex;
flex-direction: column;
width:800px;
height: 300px;
background-color: blue;
--div-width: calc(100% - 10px);
}
.item1, .item2 {
background-color: green;
height: 100px;
}
.item1 {
width: 80%;
}
.item2 {
width: 40%;
}
.included {
width: var(--div-width);
background-color:yellow;
height:50px;
}
Note I did the test inside a custom element, hence the :host
descriptor, however that is not important.
What is important is that I set --div-width
as calc(100% - 10px)
inside a div that is 800px wide - so would expect it to have a value of 790px.
However , from this screenshot
It is obvious that the calculation is being delayed until the place where the variable is used. as the yellow boxes are just 10px short of the surrounding element.
Is there a way of telling css to set the value of the property as the element where it is declared, rather than where it is used.
I did try and proxy the variable - like this ...
--proxy-width: calc(100% - 10px);
--div-width: var(--proxy-width);
but it made no difference.
PS This is a trivial example, how I actually want to use it is to control the width of an item (a textarea) inside a custom element, dependent on the context, so I can make the element responsive to changes of the width of some outer container.
Upvotes: 3
Views: 3789
Reputation: 5001
I found a "perfect" work around!.
The solution to this is to use the 'resize' event to read the size of the element you wanted use 100% on using getBoundingClientRect();
and using the width returned to set the "calc" as width px - whatever
So for the example in the question I would use this code
_resize() {
if (this.resizeInProgress) return;
this.resizeInProgress = true;
requestAnimationFrame(() => {
const container = this.shadowRoot.querySelector('.flex');
const bound = container.getBoundingClientRect();
container.style.setProperty('--div-width', `calc(${bound.width}px - 10px)`);
this.resizeInProgress = false;
});
}
I bind that function to this
in my custom element constructor
this.resizeInProgress = true;
this._resize = this._resize.bind(this);
and this in the connectedCallback
do
window.addEventListener('resize', this._resize);
this.resizeInProgress = false;
and in my disconnectedCallback
remove it again with
this.resizeInProgress = true;
window.removeEventListener('resize', this._resize);
I retain the calc()
function because in my real life cases as the amount subtracted is in "em" units
Upvotes: 1
Reputation: 4441
If I understand correctly, you want to make an element responsive to changes in size relative to a parent container. The way I've done this (but relative to the viewport) is by using relative length units like vmin
along with calc()
and CSS variables. This way, we can create a "responsive unit" whether its relative to the initial containing block or some other positioned ancestor.
The example below shows a <div>
with nested <textarea>
that is powered by responsive units. Since this width
and height
calculation is being done relative to the viewport even though the <textarea>
is a child of the div. You could possibly swap vmin
for a relative length unit which isn't relative to the viewport but relative to some ancestor like the custom element.
.wrapper {
border: 1px solid;
padding: 1rem;
}
textarea {
--w: 150;
--h: 80;
width: 200px; /* fallback width */
height: 200px; /* fallback height */
width: calc(var(--w) * 1vmin);
height: calc(var(--h) * 1vmin);
max-width: 100%;
/* extra styles for demo */
border: 1px solid red;
margin: 0 auto;
}
<div class="wrapper">
<textarea id="demo" name="demo"
rows="5" cols="30">It was a dark and stormy night...
</textarea>
</div>
Upvotes: 1