cssreactjscss-houdinicss-paint-api

Can't do JS-in-CSS for css-houdini paint?


I'm trying to copy this example; I have a scss file that I'm using with modular CSS in a React/Electron project. I want to define the function to be used by paint in the same file, as in the example:

.container {
  --background-canvas: (ctx, geom) => {
    // left blank for minimal example
  };
  background-image: paint(background-canvas);
  display: flex;
  margin: 4px;
  border-radius: 12px;
  height: 75px;
}

However, this fails to compile with CssSyntax error: Expected a pseudo-class or pseudo-element. (2:23). What am I not doing that the demo is?


Solution

  • Alright, I got it mostly working. The only part that isn't working is the transition which I'm not sure why it isn't.

    -- Edit: I got that working via: https://css-houdini.rocks/animating-gradient

      CSS.registerProperty({
        name: '--multiplier',
        syntax: '<number>',
        inherits: false,
        initialValue: 0
      })
    

    I couldn't find a way to get the CSS in JS parser to treat { } as a part of a string rather than special characters, so I used an array to allow me to run the relevant function calls in the background-canvas function.

      --background-canvas: (ctx, geom) => [ ctx.moveTo(0, 0),
        ctx.lineTo(
          var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier),
          0
        ),
        ctx.lineTo(
          var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier) +
            var(--slant),
          geom.height
        ),
        ctx.lineTo(0, geom.height), ctx.fillStyle = \`var(--color)\`, ctx.fill() ];
    

    The real fun part about this solution is that you still need to actually register the paint function.

    I did that in a similar way as a previous answer I have: https://stackoverflow.com/a/61966697/13175138 which uses this https://twitter.com/DasSurma/status/983305990731894785

    As a note, this solution from that example uses eval as part of it's registerPaint function, so this could be problematic from a security standpoint, though the paint code should theoretically be sandboxed from the main runtime.

    const Demo = styled.div`
      background: #1108a0;
      padding: 50px 0;
    `;
    const Test = styled.div`
      --color: cyan;
      --multiplier: 0.24;
      --pad: 30;
      --slant: 20;
      --background-canvas: (ctx, geom) => [ ctx.moveTo(0, 0),
        ctx.lineTo(
          var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier),
          0
        ),
        ctx.lineTo(
          var(--pad) + (geom.width - var(--slant) - var(--pad)) * var(--multiplier) +
            var(--slant),
          geom.height
        ),
        ctx.lineTo(0, geom.height), ctx.fillStyle = \`var(--color)\`, ctx.fill() ];
      background: paint(background-canvas);
      transition: --multiplier 0.4s;
      font: bold 6em sans-serif;
      color: yellow;
      text-shadow: 0 3px 1px cyan;
      line-height: 1.5em;
      width: max-content;
      padding-left: 30px;
      padding-right: 50px;
      isolation: isolate;
      &:hover {
        --multiplier: 1;
      }
      & span {
        mix-blend-mode: exclusion;
      }
    `;
    const App = () => (
      <Demo>
        <Test className="el" right={'right'}>
          <span>JS-in-CSS</span>
        </Test>
      </Demo>
    );
    ReactDOM.render(<App />, document.getElementById('root'));
    
    /*if ("paintWorklet" in CSS) {
      console.log('here')
      const src = document.querySelector('script[language$="paint"]').innerHTML;
      const blob = new Blob([src], {
        type: 'text/javascript'
      });
      CSS.paintWorklet.addModule(URL.createObjectURL(blob));
    }*/
    @media (max-width: 900px) {
      .el {
        font-size: 4em;
      }
    }
    @media (max-width: 500px) {
      .el {
        font-size: 2.4em;
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/react-is@17.0.1/umd/react-is.production.min.js"></script>
    <script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
    <div id="root"/>
    
    <script language="javascript+paint">
      registerPaint('background-canvas', class {
      static get inputProperties() {
          return ['--background-canvas'];
      }
      paint(ctx, geom, properties) {
          eval(properties.get('--background-canvas').toString())(ctx, geom, properties);
      }
    })
    </script>
    
    <script>
      // Register the property so it become animatable
      CSS.registerProperty({
        name: '--multiplier',
        syntax: '<number>',
        inherits: false,
        initialValue: 0
      })
      if ("paintWorklet" in CSS) {
      const src = document.querySelector('script[language$="paint"]').innerHTML;
      const blob = new Blob([src], {
        type: 'text/javascript'
      });
      CSS.paintWorklet.addModule(URL.createObjectURL(blob));
    }
    </script>