javascriptcssreactjstypescriptcss-houdini

Border-image conic-gradient example does not work in React


I am working to redo this Codepen in React Typescript. I found it in the blogpost here Simple way - creating React App and adding into css file it works perfect. No I tried the way with styled components and it seems I am missing something as it does not work yet?

App.tsx

import React from "react";
import "./App.css";
import styled from "styled-components";

/* Animate when Houdini is available */
const Houdini = styled.div`
width: 400px;
height: 300px;
border-radius: 10px;
padding: 2rem;
margin: auto;

display: grid;
place-content: center;
text-align: center;

font-size: 1.5em;

--border-size: 0.3rem;
border: var(--border-size) solid transparent;

/* Paint an image in the border */
border-image: conic-gradient(
    from var(--angle),
    #d53e33 0deg 90deg,
    #fbb300 90deg 180deg,
    #377af5 180deg 270deg,
    #399953 270deg 360deg
  )
  1 stretch;
background: rgb(255 255 255 / var(--opacity));

@supports (background: paint(houdini)) {
  @property --opacity {
    syntax: "<number>";
    initial-value: 0.5;
    inherits: false;
  }

  @property --angle {
    syntax: "<angle>";
    initial-value: 0deg;
    inherits: false;
  }

  @keyframes opacityChange {
    to {
      --opacity: 1;
    }
  }

  @keyframes rotate {
    to {
      --angle: 360deg;
    }
  }

  .rainbow {
    animation: rotate 4s linear infinite, opacityChange 3s infinite alternate;
  }

  /* Hide the warning */
  .warning {
    display: none;
  }
}
`;

function App() {
  return (
    <>
      <Houdini>
        <p>
          This demo uses a real border with <code>border-image</code>, a
          background, and finally Houdini to animate.
        </p>
      </Houdini>

      <div>
        <p>
          ⚠️ Your browser does not support{" "}
          <a href="https://web.dev/css-individual-transform-properties/">
            @property
          </a>{" "}
          so the animation won’t work
          <br />
          Please use Chrome.
        </p>
      </div>
    </>
  );
}

export default App;

And a slight variation the working Codepen And the change in my App.tsx the rest is the same like above - same issue, I used styled components and the effect does not show.

const Houdini = styled.div`
.
.
.
--border-size: 0.3rem;
border: var(--border-size) dotted transparent;
  background-image: linear-gradient(
      to right,
      rgb(255 255 255 / var(--opacity)),
      rgb(255 255 255 / var(--opacity))
    ),
    conic-gradient(
      from var(--angle),
      #d53e33 0deg 90deg,
      #fbb300 90deg 180deg,
      #377af5 180deg 270deg,
      #399953 270deg 360deg
    );
  background-origin: border-box;
  background-clip: padding-box, border-box;
.
.
.

`;

Solution

  • The culprit is the nested CSS rule that launches the animation:

    .rainbow {
      animation: rotate 4s linear infinite, opacityChange 3s infinite alternate;
    }
    

    ...which, in the CodePen sample, targets the element with the border:

    <div class="rainbow">
      <p>This demo uses a real border with <code>border-image</code>,
      a background, and finally Houdini to animate.</p>
    </div>
    

    But once converted to styled-components, the <div> with the "rainbow" class name was replaced by the <Houdini> styled React component. Hence its class name is no longer "rainbow", but generated by styled-components.

    <Houdini> // styled-components replaces it by something like "<div class="sc-bczRLJ kCseJt">"
      <p>
        This demo uses a real border with <code>border-image</code>, a
        background, and finally Houdini to animate.
      </p>
    </Houdini>
    

    In order to achieve the same effect (i.e. preparation then a nested rule to launch the animation, applied on the same class name), we can simply use the & (ampersand) identifier, that styled-components replaces by the generated class name (SASS/SCSS technique):

    https://styled-components.com/docs/basics#pseudoelements-pseudoselectors-and-nesting

    & a single ampersand refers to all instances of the component; it is used for applying broad overrides

    & /*.rainbow*/ {
      animation: rotate 4s linear infinite, opacityChange 3s infinite alternate;
    }
    

    ...and now the animation works!

    Note: do not forget to also define your initial CSS variables (e.g. in the global CSS file):

    :root {
      --angle: 45deg;
      --opacity: 0.5;
    }
    
    *,
    *::before,
    *::after {
      box-sizing: border-box;
    }
    

    Demo on CodeSandbox: https://codesandbox.io/s/winter-dream-ej7yec?file=/src/App.tsx:1069-1176