4
:host([aspect-ratio='square']) img {
      aspect-ratio: var(--ratio-square);
    }
    :host([aspect-ratio='landscape']) img {
      aspect-ratio: var(--ratio-landscape);
    }
    :host([aspect-ratio='portrait']) img {
      aspect-ratio: var(--ratio-portrait);
    }
    :host([aspect-ratio='widescreen']) img {
      aspect-ratio: var(--ratio-widescreen);
    }
    :host([aspect-ratio='ultrawide']) img {
      aspect-ratio: var(--ratio-ultrawide);
    }
    :host([aspect-ratio='golden']) img {
      aspect-ratio: var(--ratio-golden);
    }

How can I make it to a single line? by reducing the duplicates of this code

  • Your method is probably wrong, there is no advantage here of using css var() . `:host([aspect-ratio]) img {aspect-ratio: var(--ratio); }` should be your selector, while the value of --ratio is set ealier in your code – G-Cyrillus Aug 05 '22 at 07:17
  • @G-Cyrillus they would still need to have somewhere a long list of `[aspect-ratio="AAAA"] { --ratio: var(--ratio-AAAA) } [aspect-ratio="AAAA"] { --ratio: var(--ratio-BBBB) }`. That would just shave a few characters. – Kaiido Aug 05 '22 at 07:19
  • @Kaiido nop, if this is so, then it is also a misusue of css var() . ( when i mean *earlier in your code* doesn't mean earlier in the css , he obviously uses javascript, and it's probably there that the var() can be applied on the fly . --ratio , not --ratio-x – G-Cyrillus Aug 05 '22 at 07:20
  • That sounds like a very strong opinion on your side from the very little we know about the current situation. We don't know how they do compose these `--ratio-XXX` and they may very well need that inter-step. Regarding your edit, how do you know they use JS at all? This file may very well just be one of multiple modules, and the actual values are set in a parent. – Kaiido Aug 05 '22 at 07:22
  • @indeed, we are missing parts of the situation. – G-Cyrillus Aug 05 '22 at 07:25

2 Answers2

4

I don't think you can reasonably do this with a single line.

You could do simplify it with Sass/Scss:

$ratios: "square", "landscape", "portrait", "widescreen", "ultrawide", "golden";

@each $ratio in $ratios {
    :host([aspect-ratio='#{$ratio}']) img {
      aspect-ratio: var(--ratio-#{$ratio});
    }
}

Which generates the CSS:

:host([aspect-ratio='square']) img {
    aspect-ratio: var(--ratio-square);
}

:host([aspect-ratio='landscape']) img {
    aspect-ratio: var(--ratio-landscape);
}

:host([aspect-ratio='portrait']) img {
    aspect-ratio: var(--ratio-portrait);
}

:host([aspect-ratio='widescreen']) img {
    aspect-ratio: var(--ratio-widescreen);
}

:host([aspect-ratio='ultrawide']) img {
    aspect-ratio: var(--ratio-ultrawide);
}

:host([aspect-ratio='golden']) img {
    aspect-ratio: var(--ratio-golden);
}
phuzi
  • 12,078
  • 3
  • 26
  • 50
0

I can't think of any CSS only solution here. You'd basically need to be able to read the content of the attribute (attr() should be able to do that), and then use it to compose a new CSS variable. This last step is more problematic, I don't think there is any way to compose strings in CSS and pass them to a func like var().

I would have hoped for the new @property rule to allow us to do something around that, but when I tried to reference var(--css-var) values as syntax that simply failed (with no real surprises I must admit). And even if that did work, I'm not sure the attr() could have been used there either. attr() produces <string> and I don't think there is a way to convert a <string> to a variable name.

So the best I can think of as a CSS+HTML only solution is to modify your HTML code so that instead of doing

<my-elem aspect-ratio="square">content</my-elem>

You do

<my-elem style="--ratio:var(--ratio-square)">content</my-elem>

As you can see, that's a bit more character but stays relatively readable, and then your CSS can become

:host([style*="--ratio:"]) img { aspect-ratio: var(--ratio) }

class MyElem extends HTMLElement {
  constructor() {
    super();
    const style = document.createElement('style');
    const img = new Image();
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(style);
    shadowRoot.appendChild(img);

    style.textContent = `
img { width: 150px; border: 1px solid; }
:host([style*="--ratio:"]) img {
  aspect-ratio: var(--ratio);
}    
`;
  }
}
customElements.define("my-elem", MyElem);
:root {
  --ratio-square: 1/1;
  --ratio-landscape: 4/3;
  --ratio-golden: 1/1.618;
}
<my-elem style="--ratio:var(--ratio-square)"></my-elem>
<my-elem style="--ratio:var(--ratio-landscape)"></my-elem>
<my-elem style="--ratio:var(--ratio-golden)"></my-elem>

But given that you are in a ShadowDOM, you can actually keep that attribute and use your CustomElement's constructor to set the proper CSS variable at construct:

class MyElem extends HTMLElement {
  constructor() {
    super();
    const style = document.createElement('style');
    const img = new Image();
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(style);
    shadowRoot.appendChild(img);
    const ratio = this.getAttribute("aspect-ratio");
    style.textContent = `
img { width: 150px; border: 1px solid; }
:host([aspect-ratio]) img {
  aspect-ratio: var(--ratio-${ratio});
}    
`;
  }
}
customElements.define("my-elem", MyElem);
:root {
  --ratio-square: 1/1;
  --ratio-landscape: 4/3;
  --ratio-golden: 1/1.618;
}
<my-elem aspect-ratio="square"></my-elem>
<my-elem aspect-ratio="landscape"></my-elem>
<my-elem aspect-ratio="golden"></my-elem>
Kaiido
  • 123,334
  • 13
  • 219
  • 285