Will Doenlen

A tiny shader component for Astro


Here’s a small custom web component that you can use in Astro for running a WebGL fragment shader. It uses glslCanvas to take care of the boilerplate of setting up the shader so you’ll need to install that first. It also includes a classNames prop for Tailwind classes to style the canvas element. The code prop is the shader code as a string. I use Vite for builds and typically import the shader as a string from its own dedicated file with vite-plugin-glsl.

---
interface Props {
  code?: string;
  width?: number;
  height?: number;
  classNames?: string;
}

const defualtFragmentShader = `
  precision mediump float;
  uniform vec2 u_resolution;
  uniform float u_time;

  void main() {
      vec2 st = gl_FragCoord.xy/u_resolution.xy;
      gl_FragColor=vec4(st.x, st.y, abs(sin(u_time)), 1.0);
  }
`;

const {
  code = defualtFragmentShader,
  width = 512,
  height = 512,
  classNames = "",
}: Props = Astro.props;
---

<custom-shader>
  <script type="x-shader/x-fragment" set:text={code} />
  <canvas class={classNames} width={width} height={height}></canvas>
</custom-shader>
<script>
  import GlslCanvas from "glslCanvas";

  class Shader extends HTMLElement {
    constructor() {
      super();
      const canvas = this.querySelector("canvas");
      const shaderCode = (this.querySelector("script") as HTMLScriptElement)
        .text;
      const sandbox = new GlslCanvas(canvas);
      sandbox.load(shaderCode);
    }
  }

  customElements.define("custom-shader", Shader);
</script>

And then to use it:

import MyFragmentShader from "~/shaders/MyFragmentShader.glsl";
import Shader from "~/components/Shader.astro";

<div>
  <Shader code={MyFragmentShader} classNames="rounded" />
</div>;

which will give you the following (using the default fragment shader):