akc42
akc42

Reputation: 5001

How can I assign the value of a css calc() to a css-variable, and not have it delay the calculation until the css-variable is used

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 enter image description here

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

Answers (2)

akc42
akc42

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

Tanner Dolby
Tanner Dolby

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

Related Questions