{"id":15021302,"url":"https://github.com/carelesscourage/moonbow","last_synced_at":"2025-10-28T03:31:01.703Z","repository":{"id":65361594,"uuid":"567924470","full_name":"CarelessCourage/Moonbow","owner":"CarelessCourage","description":"Vue component for adding GLSL to images. Example site: https://moonbow.netlify.app/","archived":false,"fork":false,"pushed_at":"2023-05-30T22:15:03.000Z","size":178,"stargazers_count":33,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-03T09:34:25.358Z","etag":null,"topics":["glsl","shaders","three","threejs","typescript","vue","vue3","webgl"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CarelessCourage.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-18T23:03:45.000Z","updated_at":"2024-08-14T06:28:14.000Z","dependencies_parsed_at":"2024-09-23T22:02:15.971Z","dependency_job_id":null,"html_url":"https://github.com/CarelessCourage/Moonbow","commit_stats":{"total_commits":78,"total_committers":1,"mean_commits":78.0,"dds":0.0,"last_synced_commit":"24f8b1a084ef494cc5f58e5fcec600c809ee8d13"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarelessCourage%2FMoonbow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarelessCourage%2FMoonbow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarelessCourage%2FMoonbow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarelessCourage%2FMoonbow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CarelessCourage","download_url":"https://codeload.github.com/CarelessCourage/Moonbow/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238590593,"owners_count":19497351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["glsl","shaders","three","threejs","typescript","vue","vue3","webgl"],"created_at":"2024-09-24T19:56:25.368Z","updated_at":"2025-10-28T03:31:01.192Z","avatar_url":"https://github.com/CarelessCourage.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![chrome-capture-2023-1-5 (1)](https://user-images.githubusercontent.com/12764398/216837975-c1074633-c5be-41f4-b2ca-2c52806eff94.gif)\n\n# Moonbow :new_moon_with_face:+:rainbow:\nVue img component for adding [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to images :fire::fire::fire: \n\nTest it out yourself. Head over to [Moonbow](https://moonbow.netlify.app/) to get an idea of whats possible and feel out the performance. \nOr jump right into a code example with this [StackBlitz](https://stackblitz.com/edit/vitejs-vite-334uch?file=src%2FApp.vue,src%2Fstyle.css).\n\n## :alembic: How it works\n\u003e Moonbow leverages [three.js](https://github.com/mrdoob/three.js/) to create a 3D space in webGL. It creates a 3 dimensional plane for each image and sticks it to the size and position of the proxy HTML img element. It re-attatches this plane to the img element on every animation frame to keep it consistent with the layout. But it saves on performance by only attaching the planes that are inside the viewport. It does this by using an intersection observer to check for images in view.\n\n- :kissing_cat: ***Simple*** - Just a simple image component needed. Nothing more\n- :muscle: ***Flexible*** - Flexible primitives underneath that let you build your own logic\n- :telescope: ***Typesafe*** - Written fully in typescript \n- :hammer_and_wrench: ***Maintainable*** - HTML stays descriptive of content letting canvas images flows with the HTML elements\n- :man_in_manual_wheelchair: ***Accessible*** - Since canvas elements have their HTML counterparts you dont lose accessibility controls like other approaches would\n\n#### :test_tube: Benefits of this approach\nThis apprach lets you take advantage of GLSL for your images while keeping the DOM descriptive of your content. Images get created in webGL with a HTML proxy element in the DOM taking up space and flowing with your layout. This is also great for accessibility since it means we can accomplish complex image manipulation without sacrificing on the browser inbuilt accessibility tools.\n\n## :scroll: Resources\nLearn how to write GLSL: [Book of Shaders](https://thebookofshaders.com/)\n\n## :package: Installation\n```bash\nnpm install moonbow\n```\n\n## :building_construction: Setup\nYou can inject GLSL as a string but if you want to actually use it you're going to want to store GLSL in files that you can import. In order to do that you need to let Vite know how to handle GLSL files though. Import the [vite-plugin-glsl](https://github.com/UstymUkhman/vite-plugin-glsl) package and register it as a plugin in your vite.config file.\n\n```js\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport glsl from 'vite-plugin-glsl'\n\nexport default defineConfig({\n  plugins: [vue(), glsl()],\n})\n```\n\n## :crystal_ball: Usage\nSimple example using default GLSL. Here's a [StackBlitz](https://stackblitz.com/edit/vitejs-vite-334uch?file=src%2FApp.vue,src%2Fstyle.css) if you just want to jump right into it as fast as possible.\n```vue\n\u003cscript setup\u003e\nimport { Moon } from \"moonbow\"\nimport \"moonbow/dist/style.css\"\nconst imageURL = \"https://images.unsplash.com/photo-1642059893618\"\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cMoon :src=\"imageURL\"/\u003e\n\u003c/template\u003e\n```\n\nAdding your own custom GLSL\n```vue\n\u003cscript setup\u003e\nimport { Moon } from \"moonbow\"\nimport \"moonbow/dist/style.css\"\n\nimport vertexShader from '../shaders/scrollDeform/vertex.glsl'\nimport fragmentShader from '../shaders/scrollDeform/fragment.glsl'\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cMoon \n    src=\"https://images.unsplash.com/photo-1642059893618\" \n    :vertexShader=\"vertexShader\"\n    :fragmentShader=\"fragmentShader\"\n  /\u003e\n\u003c/template\u003e\n```\n\nAdding custom uniforms and changing uniforms dynamically from JavaScript\n```vue\n\u003cscript setup\u003e\nimport { watch } from 'vue'\nimport { Moon, useScroll } from \"moonbow\"\nimport \"moonbow/dist/style.css\"\n\nimport vertexShader from '../shaders/scrollDeform/vertex.glsl'\nimport fragmentShader from '../shaders/scrollDeform/fragment.glsl'\n\nlet uniforms = {\n  uVelocity: { value: 0 },\n}\n\nconst velocity = useScroll()\nconst uniformControls = {\n  vertexShader,\n  fragmentShader,\n  uniforms,\n  uniformAction: (material) =\u003e {\n    watch(velocity, (velocity) =\u003e {\n      material.uniforms.uVelocity.value = velocity\n    })\n  }\n}\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cMoon\n    src=\"https://images.unsplash.com/photo-1642059893618\"\n    v-bind=\"uniformControls\"\n  /\u003e\n\u003c/template\u003e\n```\n\nMost of the time you will probably want more than one image. And these multiple images probably will use the same effect, sometimes maybe even in different files. Setting the same shader object over and over can be cumbersome. So I've provided a function for setting the default shader for all images. Just set this once and never worry about setting it again unless you want to overwrite the defaults on a specific image.\n```vue\n\u003cscript setup\u003e\nimport { watch } from 'vue'\nimport { Moon, useScroll, defaultGLSL } from \"moonbow\"\nimport \"moonbow/dist/style.css\"\n\nimport vertexShader from '../shaders/scrollDeform/vertex.glsl'\nimport fragmentShader from '../shaders/scrollDeform/fragment.glsl'\n\nlet uniforms = {\n  uVelocity: { value: 0 },\n}\n\nconst scroll = useScroll()\nfunction uniformAction(material: any) {\n  watch(scroll, (s) =\u003e {\n    material.uniforms.uVelocity.value = s\n  })\n}\n\ndefaultGLSL({\n  uniforms,\n  vertexShader,\n  fragmentShader,\n  uniformAction\n})\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cMoon src=\"https://images.unsplash.com/photo-1642059893618\"/\u003e\n  \u003cMoon src=\"https://images.unsplash.com/photo-1642059893618\"/\u003e\n  \u003cMoon src=\"https://images.unsplash.com/photo-1642059893618\"/\u003e\n  \u003cMoon src=\"https://images.unsplash.com/photo-1642059893618\"/\u003e\n  \u003cMoon src=\"https://images.unsplash.com/photo-1642059893618\"/\u003e\n\u003c/template\u003e\n```\n\n## :dna: Primitives\nMoonbow exposes the underlying primitives developed to make the Moon image component. You can easily use these primitives to build your own solution. Below is the entire code for the Moon component showcasing the way it uses the primities. \n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport { ref, onMounted  } from \"vue\"\nimport { useShader, scene, useImage, syncProxyHTML } from \"moonbow\";\n\nconst props = defineProps({\n  src: string;\n  vertexShader?: string;\n  fragmentShader?: string;\n  uniforms?: any;\n  uniformAction?: (material: ShaderType) =\u003e void;\n})\n\nconst imageRef = ref(null)\n\nonMounted(() =\u003e {\n  const src = props.src;\n  const element = imageRef.value\n  const scene = sceneRef.value\n  const shader: MoonbowShader = {\n    vertexShader: props.vertexShader,\n    fragmentShader: props.fragmentShader,\n    uniforms: props.uniforms,\n    uniformAction: props.uniformAction\n  }\n\n  const material = useShader(element, shader)\n  const proxy = useImage({scene, element, material})\n  syncProxyHTML({ proxy, src })\n})\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cdiv class=\"img-wrapper\"\u003e\n    \u003cimg :src=\"src\" ref=\"imageRef\"/\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\nPostprocessing function to let you apply GLSL to all elements uniformly. Example with plain GLSL without dynamic uniforms\n```vue\n\u003cscript setup\u003e\nimport { postProcessing } from '../composables/canvas'\n\nimport vertexShader from '../shaders/bottomScale/vertex.glsl';\nimport fragmentShader from '../shaders/bottomScale/fragment.glsl';\n\nconst uniforms = {\n  uExample: { value: 0 },\n}\n\npostProcessing({\n  uniforms,\n  vertexShader,\n  fragmentShader,\n})\n\u003c/script\u003e\n```\n\nPostprocessing function with uniformAction/dynamic uniforms example.\n```vue\n\u003cscript setup\u003e\nimport { postProcessing } from '../composables/canvas'\n\nimport vertexShader from '../shaders/bottomScale/vertex.glsl';\nimport fragmentShader from '../shaders/bottomScale/fragment.glsl';\n\nconst uniforms = {\n  uExample: { value: 0 },\n}\n\nconst shader = {\n  uniforms,\n  vertexShader,\n  fragmentShader,\n  uniformAction: (m) =\u003e {\n    watch(velocity, (velocity) =\u003e {\n      m.uniforms.uVelocity.value = velocity\n    })\n  }\n}\n\npostProcessing(shader)\n\u003c/script\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarelesscourage%2Fmoonbow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcarelesscourage%2Fmoonbow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarelesscourage%2Fmoonbow/lists"}