1

I've successfully created a css variable animations, using the experimental @property feature.

IMPORTANT: this only works in Chrome, Opera or Edge compatibility table

document.addEventListener('DOMContentLoaded', () => {
  const rangeInput = document.querySelector('#scale');
  const htmlElement = document.querySelector('#html-element');
  rangeInput.addEventListener('change', (e) => {
    htmlElement.style.setProperty(
      '--html-element-scale',
      (e.target.value / 100).toFixed(2)
    );
  });
});
@property --html-element-background-color {
  syntax: '<color>';
  inherits: false;
  initial-value: #00ff00;
}

@property --html-element-rotate {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

@property --html-element-scale {
  syntax: '<number>';
  inherits: false;
  initial-value: 1;
}

#html-element {
  transform: rotate(var(--html-element-rotate))
    scale(var(--html-element-scale));

  border: 1px solid #000;
  height: 100px;
  width: 100px;
  background-color: var(--html-element-background-color);
  animation: anim 1s ease-in infinite alternate;
}

@keyframes anim {
  100% {
    --html-element-rotate: 180deg;
    --html-element-background-color: #ff00ff;
  }
}
<div id="html-element">HTML Element</div>

<div id="range-container">
  <input
    type="range"
    id="scale"
    name="scale"
    min="0"
    max="100"
    value="100"
  />
  <label for="scale">Scale</label>
</div>

But when wrapping it inside of a web-component, only the css values are changed, but the values of the css var's are not animated. Furthermore the rotation doesn't work, until you change the scale slider or alternatively ad a :host {--html-element-scale: 1} as default value... which is weird.

const html = `
<style>
  @property --web-component-background-color {
    syntax: '<color>';
    inherits: false;
    initial-value: #00ff00;
  }

  @property --web-component-rotate {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0deg;
  }

  @property --web-component-scale {
    syntax: '<number>';
    inherits: false;
    initial-value: 1;
  }

  #web-component {
    transform: scale(var(--web-component-scale)) rotate(var(--web-component-rotate));

    border: 1px solid #000;
    height: 100px;
    width: 100px;
    background-color: var(--web-component-background-color);
    animation: anim 1s ease-in infinite alternate;
  }

  @keyframes anim {
    100% {
      --web-component-rotate: 180deg;
      --web-component-background-color: #ff00ff;
    }
  }
</style>

<div id="web-component">Web Component</div>
<div id="range-container">
  <input type="range" id="scale" name="scale" min="0" max="100" value="100" />
  <label for="scale">Scale</label>
</div>`;

customElements.define(
  'web-component',
  class WebComponent extends HTMLElement {
    shadowRoot = this.attachShadow({ mode: 'open' });
    constructor() {
      super();
      let template = document.createElement('template');
      template.innerHTML = html;
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
    connectedCallback() {
      const rangeInput = this.shadowRoot.querySelector('#scale');
      const webComponent = this.shadowRoot.querySelector('#web-component');
      rangeInput.addEventListener('change', (e) => {
        webComponent.style.setProperty(
          '--web-component-scale',
          (e.target.value / 100).toFixed(2)
        );
      });
    }
  }
);
<web-component></web-component>

Last but not least, if you copy the stylesheet from the web-component into the regular dom, so that the web-component inherits the css, it works again.

The question is, is this a bug due to the experimental stage of the feature or am I doing something wrong with implementing it in a web-component?

Marcello di Simone
  • 1,612
  • 1
  • 14
  • 29

1 Answers1

2

It looks like @property definitions do not work inside of web-components. I'm not sure if this is intended or a bug, I will report it as an issue to the css-houdini project.

However, if you're interested in what a working version would look like if you only want to use globally defined css variables for animations (which is quite limited in it's use case for a web-component)

const html = `
<style>
  #web-component {
    transform: rotate(var(--html-element-rotate))
      scale(var(--html-element-scale));
    border: 1px solid #000;
    height: 100px;
    width: 100px;
    background-color: var(--html-element-background-color);
    animation: anim 1s ease-in infinite alternate;
  }

  @keyframes anim {
    100% {
      --html-element-rotate: -180deg;
      --html-element-background-color: #0000ff;
    }
  }
</style>

<div id="web-component">Web Component</div>
<div id="range-container">
  <input type="range" id="scale" name="scale" min="0" max="100" value="100" />
  <label for="scale">Scale</label>
</div>`;

document.addEventListener('DOMContentLoaded', () => {
  const rangeInput = document.querySelector('#scale');
  const htmlElement = document.querySelector('#html-element');
  rangeInput.addEventListener('change', (e) => {
    htmlElement.style.setProperty(
      '--html-element-scale',
      (e.target.value / 100).toFixed(2)
    );
  });
});


customElements.define(
  'web-component',
  class WebComponent extends HTMLElement {
    shadowRoot = this.attachShadow({ mode: 'open' });
    constructor() {
      super();
      let template = document.createElement('template');
      template.innerHTML = html;
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
    connectedCallback() {
      const rangeInput = this.shadowRoot.querySelector('#scale');
      const webComponent = this.shadowRoot.querySelector('#web-component');
      rangeInput.addEventListener('change', (e) => {
        webComponent.style.setProperty(
          '--html-element-scale',
          (e.target.value / 100).toFixed(2)
        );
      });
    }
  }
);
@property --html-element-background-color {
  syntax: '<color>';
  inherits: false;
  initial-value: #00ff00;
}

@property --html-element-rotate {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

@property --html-element-scale {
  syntax: '<number>';
  inherits: false;
  initial-value: 1;
}

#html-element {
  transform: rotate(var(--html-element-rotate))
    scale(var(--html-element-scale));
  border: 1px solid #000;
  height: 100px;
  width: 100px;
  background-color: var(--html-element-background-color);
  animation: anim 1s ease-in infinite alternate;
}

@keyframes anim {
  100% {
    --html-element-rotate: 180deg;
    --html-element-background-color: #ff00ff;
  }
}
<web-component></web-component>


<div id="html-element">HTML Element</div>

<div id="range-container">
  <input
    type="range"
    id="scale"
    name="scale"
    min="0"
    max="100"
    value="100"
  />
  <label for="scale">Scale</label>
</div>
Marcello di Simone
  • 1,612
  • 1
  • 14
  • 29