npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

moonbow

v1.0.32

Published

Vue component for adding GLSL to images

Downloads

35

Readme

chrome-capture-2023-1-5 (1)

Moonbow :new_moon_with_face:+:rainbow:

Vue img component for adding GLSL to images :fire::fire::fire:

Test it out yourself. Head over to Moonbow to get an idea of whats possible and feel out the performance.

:alembic: How it works

Moonbow leverages 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.

  • :kissing_cat: Simple - Just a simple image component needed. Nothing more
  • :muscle: Flexible - Flexible primitives underneath that let you build your own logic
  • :telescope: Typesafe - Written fully in typescript
  • :hammer_and_wrench: Maintainable - HTML stays descriptive of content letting canvas images flows with the HTML elements
  • :man_in_manual_wheelchair: Accessible - Since canvas elements have their HTML counterparts you dont lose accessibility controls like other approaches would

:test_tube: Benefits of this approach

This 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.

:scroll: Resources

Learn how to write GLSL: Book of Shaders

:package: Installation

npm install moonbow

:building_construction: Setup

You 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 package and register it as a plugin in your vite.config file.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import glsl from 'vite-plugin-glsl'

export default defineConfig({
  plugins: [vue(), glsl()],
})

:crystal_ball: Usage

Simple example using default GLSL

<script setup>
import { Moon } from "moonbow"
import "moonbow/dist/style.css"
const imageURL = "https://images.unsplash.com/photo-1642059893618"
</script>

<template>
  <Moon :src="imageURL"/>
</template>

Adding your own custom GLSL

<script setup>
import { Moon } from "moonbow"
import "moonbow/dist/style.css"

import vertexShader from '../shaders/scrollDeform/vertex.glsl'
import fragmentShader from '../shaders/scrollDeform/fragment.glsl'
</script>

<template>
  <Moon 
    src="https://images.unsplash.com/photo-1642059893618" 
    :vertexShader="vertexShader"
    :fragmentShader="fragmentShader"
  />
</template>

Adding custom uniforms and changing uniforms dynamically from JavaScript

<script setup>
import { watch } from 'vue'
import { Moon, useScroll } from "moonbow"
import "moonbow/dist/style.css"

import vertexShader from '../shaders/scrollDeform/vertex.glsl'
import fragmentShader from '../shaders/scrollDeform/fragment.glsl'

let uniforms = {
  uVelocity: { value: 0 },
}

const velocity = useScroll()
const uniformControls = {
  vertexShader,
  fragmentShader,
  uniforms,
  uniformAction: (material) => {
    watch(velocity, (velocity) => {
      material.uniforms.uVelocity.value = velocity
    })
  }
}
</script>

<template>
  <Moon
    src="https://images.unsplash.com/photo-1642059893618"
    v-bind="uniformControls"
  />
</template>

Most 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.

<script setup>
import { watch } from 'vue'
import { Moon, useScroll, defaultGLSL } from "moonbow"
import "moonbow/dist/style.css"

import vertexShader from '../shaders/scrollDeform/vertex.glsl'
import fragmentShader from '../shaders/scrollDeform/fragment.glsl'

let uniforms = {
  uVelocity: { value: 0 },
}

const scroll = useScroll()
function uniformAction(material: any) {
  watch(scroll, (s) => {
    material.uniforms.uVelocity.value = s
  })
}

defaultGLSL({
  uniforms,
  vertexShader,
  fragmentShader,
  uniformAction
})
</script>

<template>
  <Moon src="https://images.unsplash.com/photo-1642059893618"/>
  <Moon src="https://images.unsplash.com/photo-1642059893618"/>
  <Moon src="https://images.unsplash.com/photo-1642059893618"/>
  <Moon src="https://images.unsplash.com/photo-1642059893618"/>
  <Moon src="https://images.unsplash.com/photo-1642059893618"/>
</template>

:dna: Primitives

Moonbow 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.

<script setup lang="ts">
import { ref, onMounted  } from "vue"
import { useShader, scene, useImage, syncProxyHTML } from "moonbow";

const props = defineProps({
  src: string;
  vertexShader?: string;
  fragmentShader?: string;
  uniforms?: any;
  uniformAction?: (material: ShaderType) => void;
})

const imageRef = ref(null)

onMounted(() => {
  const src = props.src;
  const element = imageRef.value
  const scene = sceneRef.value
  const shader: MoonbowShader = {
    vertexShader: props.vertexShader,
    fragmentShader: props.fragmentShader,
    uniforms: props.uniforms,
    uniformAction: props.uniformAction
  }

  const material = useShader(element, shader)
  const proxy = useImage({scene, element, material})
  syncProxyHTML({ proxy, src })
})
</script>

<template>
  <div class="img-wrapper">
    <img :src="src" ref="imageRef"/>
  </div>
</template>

Postprocessing function to let you apply GLSL to all elements uniformly. Example with plain GLSL without dynamic uniforms

<script setup>
import { postProcessing } from '../composables/canvas'

import vertexShader from '../shaders/bottomScale/vertex.glsl';
import fragmentShader from '../shaders/bottomScale/fragment.glsl';

const uniforms = {
  uExample: { value: 0 },
}

postProcessing({
  uniforms,
  vertexShader,
  fragmentShader,
})
</script>

Postprocessing function with uniformAction/dynamic uniforms example.

<script setup>
import { postProcessing } from '../composables/canvas'

import vertexShader from '../shaders/bottomScale/vertex.glsl';
import fragmentShader from '../shaders/bottomScale/fragment.glsl';

const uniforms = {
  uExample: { value: 0 },
}

const shader = {
  uniforms,
  vertexShader,
  fragmentShader,
  uniformAction: (m) => {
    watch(velocity, (velocity) => {
      m.uniforms.uVelocity.value = velocity
    })
  }
}

postProcessing(shader)
</script>