<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TommyDee</title>
    <description>The latest articles on DEV Community by TommyDee (@thomasdolso).</description>
    <link>https://dev.to/thomasdolso</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3935266%2Fcd3d3274-7276-4242-803d-0c0e1970ca4b.jpg</url>
      <title>DEV Community: TommyDee</title>
      <link>https://dev.to/thomasdolso</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thomasdolso"/>
    <language>en</language>
    <item>
      <title>I just open-sourced 13 MIT libraries for web visual effects</title>
      <dc:creator>TommyDee</dc:creator>
      <pubDate>Sat, 16 May 2026 17:50:36 +0000</pubDate>
      <link>https://dev.to/thomasdolso/i-just-open-sourced-13-mit-libraries-for-web-visual-effects-2g86</link>
      <guid>https://dev.to/thomasdolso/i-just-open-sourced-13-mit-libraries-for-web-visual-effects-2g86</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzh71jtnj8ny92gfju54s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzh71jtnj8ny92gfju54s.webp" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After months of work, today I shipped &lt;strong&gt;Vysmo&lt;/strong&gt; — a set of MIT-licensed libraries for web visual computing. All 13 packages are now on npm under &lt;code&gt;@vysmo&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/transitions&lt;/code&gt;&lt;/strong&gt; — 60 WebGL2 transition shaders defined as plain data. Includes a mesh-based page-curl with drag-scrub mid-flip, polygon flip, and classic crossfades/wipes. Tree-shakable to the byte.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/text&lt;/code&gt;&lt;/strong&gt; — Multi-property choreographed text animation with 300+ presets. Grapheme-safe splitting via &lt;code&gt;Intl.Segmenter&lt;/code&gt; works for emoji, Arabic, and Devanagari.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/effects&lt;/code&gt;&lt;/strong&gt; — WebGL filter primitives (blur, bloom, glow, vignette, chromatic aberration, color grading).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/easings&lt;/code&gt;&lt;/strong&gt; — 40+ named curves, parametric builders (spring, bezier, wiggle, rough), composition modifiers, CSS export, reduced-motion helpers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/scroll&lt;/code&gt;&lt;/strong&gt; — scroll-driven primitives that compose with transitions and effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/flipbook&lt;/code&gt;&lt;/strong&gt; — drag-scrub page-flip component built on the page-curl shader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/slideshow&lt;/code&gt;&lt;/strong&gt; — image slideshow with opt-in chrome, drives any of the 60 transitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/animations&lt;/code&gt;&lt;/strong&gt; — value-based tweening: &lt;code&gt;animate()&lt;/code&gt;, &lt;code&gt;spring()&lt;/code&gt;, &lt;code&gt;timeline()&lt;/code&gt;, &lt;code&gt;interpolate()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@vysmo/gl-core&lt;/code&gt;&lt;/strong&gt; — shared WebGL2 plumbing.&lt;/li&gt;
&lt;li&gt;Plus React wrappers: &lt;code&gt;@vysmo/transitions-react&lt;/code&gt;, &lt;code&gt;@vysmo/text-react&lt;/code&gt;, &lt;code&gt;@vysmo/flipbook-react&lt;/code&gt;, &lt;code&gt;@vysmo/slideshow-react&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design principles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies&lt;/strong&gt; in every package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR-safe at module load&lt;/strong&gt; — enforced by a Node import test per package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless-first&lt;/strong&gt; — components are opt-in wrappers around a vanilla TS core. The same code drives canvas, image, and video sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain-data API&lt;/strong&gt; for transitions, text, and effects so the same definition can drive DOM today and a canvas renderer later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick example
&lt;/h2&gt;

&lt;p&gt;Crossfade between two images with one of the 60 WebGL transitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paintBleed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vysmo/transitions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vysmo/animations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLCanvasElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fromImg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toImg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;fromImg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/photo-a.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;toImg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/photo-b.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;fromImg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;toImg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

&lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paintBleed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fromImg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toImg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transition is just plain data describing how to interpolate between two textures. The Runner handles WebGL plumbing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live playgrounds for every library&lt;/strong&gt;: &lt;a href="https://vysmo.com" rel="noopener noreferrer"&gt;https://vysmo.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/vysmodev/vysmo" rel="noopener noreferrer"&gt;https://github.com/vysmodev/vysmo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/org/vysmo" rel="noopener noreferrer"&gt;https://www.npmjs.com/org/vysmo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the first npm release (0.1.0), so APIs may still shift before 1.0. I'd love feedback on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API design&lt;/li&gt;
&lt;li&gt;Bugs or weird behavior in the playgrounds&lt;/li&gt;
&lt;li&gt;Demos or use-cases you'd want to see covered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webgl</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
