0

I have a custom HTML element I made that creates a repeating background pattern which I animate in. I need the ability to dynamically set the color of this pattern with a CSS variable that lives in the parent/main document scope.

I know this is possible, but I think the complication is that I am setting the BG pattern as a CSS background image string.

customElements.define(
  'svg-background',
  class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => {
        // wait till <template> is parsed as DOM
        let svg = this.querySelector('template')
          .innerHTML.replaceAll(/(\n\s*)|(\<!-- .*? --\>)/g, '') //remove linebreaks & indentations after linebreaks, and comments
          .replaceAll('"', "'") //replace double quotes
          .replaceAll('#', '%23'); //escape #
        Object.assign(this.style, {
          backgroundImage: `url("data:image/svg+xml;utf8,${svg}")`,
        });
      });
    }
  },
);

function toggleColor(){
  document.documentElement.classList.toggle('theme-blue')
}
:root{
  --hex-color: red;
}

:root.theme-blue{
  --hex-color: blue;
}

svg-background {
  position: fixed;
  bottom: 0;
  left:0;
  width: 100vw;
  height: 800px;
  transform: translateZ(0);
  pointer-events: none;
  z-index: -1;
}
<button type="button" onclick="toggleColor()">Toggle Color</button>

<svg-background>
  <template>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 800">
      <defs>
        <style>
          use {
            animation: 0.4s fade-in-hex;
            animation-fill-mode: both;
            fill: var(--app-theme-hex);
          }
          @keyframes fade-in-hex {
            from {opacity: 0;}
            to {opacity: 1;}
          }
          #hex-col use:nth-child(1) {animation-delay: 0.9s;}
          #hex-col use:nth-child(2) {animation-delay: 0.8s;}
          #hex-col use:nth-child(3) {animation-delay: 0.7s;}
          #hex-col use:nth-child(4) {animation-delay: 0.6s;}
          #hex-col use:nth-child(5) {animation-delay: 0.5s;}
          #hex-col use:nth-child(6) {animation-delay: 0.4s;}
          #hex-col use:nth-child(7) {animation-delay: 0.3s;}
          #hex-col use:nth-child(8) {animation-delay: 0.2s;}
          #hex-col use:nth-child(9) {animation-delay: 0.1s;}
        </style>
        <path id="hex" d="M0 25.980762113533157L15 0L45 0L60 25.980762113533157L45 51.96152422706631L15 51.96152422706631Z" />
      </defs>
      <!-- We repeat a pattern of 3 columns. One in the middle, and then two on either side that are cut in half so they line up when they meet -->
      <g id="hex-col">
        <use href="#hex" transform="translate(25 25) scale(.2)" />
        <use href="#hex" transform="translate(21 115) scale(.3)" />
        <use href="#hex" transform="translate(18 205) scale(.4)" />
        <use href="#hex" transform="translate(15 295) scale(.5)" />
        <use href="#hex" transform="translate(12 385) scale(.6)" />
        <use href="#hex" transform="translate(10 475) scale(.7)" />
        <use href="#hex" transform="translate(8 565) scale(.8)" />
        <use href="#hex" transform="translate(4 655) scale(.9)" />
        <use href="#hex" transform="translate(0 750)" />
      </g>
      <use href="#hex-col" transform="translate(45 -51)" />
      <use href="#hex-col" transform="translate(-45 -51)" />
    </svg>
  </template>
</svg-background>

2
  • I'm not familiar with custom html elements, but could you just give it a css class and assign the style, including the css variable, in the css class? Commented Nov 21, 2024 at 22:21
  • 1
    Maybe move everything to shadowDOM, that way you don't need the <template>, setTimeout and all replace stuff; and you can use currentColor. Maybe even generate the SVG (columns) client-side instead of a static SVG, that will make it easier to adjust spacing, scale etc. as well. Commented Nov 22, 2024 at 9:32

1 Answer 1

2

I would use it as a mask instead and control the color using the background

customElements.define(
  'svg-background',
  class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => {
        // wait till <template> is parsed as DOM
        let svg = this.querySelector('template')
          .innerHTML.replaceAll(/(\n\s*)|(\<!-- .*? --\>)/g, '') //remove linebreaks & indentations after linebreaks, and comments
          .replaceAll('"', "'") //replace double quotes
          .replaceAll('#', '%23'); //escape #
        Object.assign(this.style, {
          mask: `url("data:image/svg+xml;utf8,${svg}")`,
        });
      });
    }
  },
);

function toggleColor(){
  document.documentElement.classList.toggle('theme-blue')
}
:root{
  --hex-color: red;
}

:root.theme-blue{
  --hex-color: blue;
}

svg-background {
  position: fixed;
  bottom: 0;
  left:0;
  width: 100vw;
  height: 800px;
  transform: translateZ(0);
  pointer-events: none;
  z-index: -1;
  background: var(--hex-color)
}
<button type="button" onclick="toggleColor()">Toggle Color</button>

<svg-background>
  <template>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 800">
      <defs>
        <style>
          use {
            animation: 0.4s fade-in-hex;
            animation-fill-mode: both;
            fill: #000;
          }
          @keyframes fade-in-hex {
            from {opacity: 0;}
            to {opacity: 1;}
          }
          #hex-col use:nth-child(1) {animation-delay: 0.9s;}
          #hex-col use:nth-child(2) {animation-delay: 0.8s;}
          #hex-col use:nth-child(3) {animation-delay: 0.7s;}
          #hex-col use:nth-child(4) {animation-delay: 0.6s;}
          #hex-col use:nth-child(5) {animation-delay: 0.5s;}
          #hex-col use:nth-child(6) {animation-delay: 0.4s;}
          #hex-col use:nth-child(7) {animation-delay: 0.3s;}
          #hex-col use:nth-child(8) {animation-delay: 0.2s;}
          #hex-col use:nth-child(9) {animation-delay: 0.1s;}
        </style>
        <path id="hex" d="M0 25.980762113533157L15 0L45 0L60 25.980762113533157L45 51.96152422706631L15 51.96152422706631Z" />
      </defs>
      <!-- We repeat a pattern of 3 columns. One in the middle, and then two on either side that are cut in half so they line up when they meet -->
      <g id="hex-col">
        <use href="#hex" transform="translate(25 25) scale(.2)" />
        <use href="#hex" transform="translate(21 115) scale(.3)" />
        <use href="#hex" transform="translate(18 205) scale(.4)" />
        <use href="#hex" transform="translate(15 295) scale(.5)" />
        <use href="#hex" transform="translate(12 385) scale(.6)" />
        <use href="#hex" transform="translate(10 475) scale(.7)" />
        <use href="#hex" transform="translate(8 565) scale(.8)" />
        <use href="#hex" transform="translate(4 655) scale(.9)" />
        <use href="#hex" transform="translate(0 750)" />
      </g>
      <use href="#hex-col" transform="translate(45 -51)" />
      <use href="#hex-col" transform="translate(-45 -51)" />
    </svg>
  </template>
</svg-background>

Sign up to request clarification or add additional context in comments.

1 Comment

Excellent solution, thank you! This is why I still love StackOverflow

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.