<?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: Vladyslav</title>
    <description>The latest articles on DEV Community by Vladyslav (@penev-tech).</description>
    <link>https://dev.to/penev-tech</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%2F3671995%2F4da44d6f-79e6-411b-ae0e-5a8af572db60.png</url>
      <title>DEV Community: Vladyslav</title>
      <link>https://dev.to/penev-tech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/penev-tech"/>
    <language>en</language>
    <item>
      <title>StringTune-3D: Forcing 3D to Obey CSS</title>
      <dc:creator>Vladyslav</dc:creator>
      <pubDate>Mon, 22 Dec 2025 10:49:36 +0000</pubDate>
      <link>https://dev.to/penev-tech/string3d-forcing-3d-to-obey-css-583h</link>
      <guid>https://dev.to/penev-tech/string3d-forcing-3d-to-obey-css-583h</guid>
      <description>&lt;p&gt;There is something oddly appealing about the idea of controlling a 3D world through plain CSS.&lt;/p&gt;

&lt;p&gt;It’s like standing in the middle of a disassembled engine, covered in grease, catching yourself thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What if, instead of a wrench, I just use… &lt;code&gt;:hover&lt;/code&gt;?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Logic says this will end badly. The browser seems to agree.&lt;br&gt;
But I got hooked.&lt;/p&gt;

&lt;p&gt;I’m building &lt;strong&gt;String3D&lt;/strong&gt; — a module for &lt;a href="https://www.npmjs.com/package/@fiddle-digital/string-tune" rel="noopener noreferrer"&gt;StringTune&lt;/a&gt; that allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drop 3D objects right into an HTML layout&lt;/li&gt;
&lt;li&gt;Bind them to DOM elements&lt;/li&gt;
&lt;li&gt;Control transformations via CSS custom properties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No &lt;code&gt;mousemove&lt;/code&gt; orchestra, no “half-a-project” worth of glue code, no constant&lt;br&gt;
&lt;em&gt;“recalculate my position because the layout shifted again.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is what it looks like in action:&lt;/p&gt;


  



&lt;h2&gt;
  
  
  What this is (and what it isn’t)
&lt;/h2&gt;

&lt;p&gt;Let’s kill the magic expectations right now: &lt;strong&gt;String3D is not “CSS 3D”.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are no tricks here where the browser suddenly learned to render meshes natively.&lt;br&gt;
Under the hood, it’s a standard 3D renderer in a canvas living &lt;strong&gt;above&lt;/strong&gt; the page.&lt;/p&gt;

&lt;p&gt;The difference lies elsewhere.&lt;/p&gt;

&lt;p&gt;String3D synchronizes 3D objects with DOM elements &lt;strong&gt;every frame&lt;/strong&gt;, but it takes its behavioral instructions from &lt;strong&gt;CSS custom properties&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The DOM remains the DOM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hover&lt;/code&gt; works as usual&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;layout&lt;/code&gt; lives its normal, slightly chaotic life&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 3D layer doesn’t control the page or try to replace it.&lt;br&gt;
It just looks at the DOM and says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Show me where you are.&lt;br&gt;
What size you are.&lt;br&gt;
How you are rotating.&lt;br&gt;
I will repeat it.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The DOM is the source of truth.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The 3D is a shadow that obeys.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;
&lt;h4&gt;
  
  
  Installation
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;string-tune string-tune-3d three
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  JavaScript Initialization
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;StringTune&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string-tune&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;ThreeJSProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;String3D&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="s1"&gt;string-tune-3d&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;three&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;stringTune&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StringTune&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;String3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreeJSProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;stringTune&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;String3D&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;stringTune&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Basic HTML
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"shape"&lt;/span&gt;
  &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"3d"&lt;/span&gt;
  &lt;span class="na"&gt;string-3d=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt;
  &lt;span class="na"&gt;string-3d-material=&lt;/span&gt;&lt;span class="s"&gt;"standard[#0000ff]"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  BOX
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



  


&lt;p&gt;After this, the blue cube is tied to the element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It takes its position from the layout&lt;/li&gt;
&lt;li&gt;Its size too&lt;/li&gt;
&lt;li&gt;It picks up resizes without a “please update” plea&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, a cube in the DOM isn’t the peak of human intellect.&lt;br&gt;
But this is the moment you realize:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Okay, this actually shapes up into a decent abstraction.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  CSS as the 3D Control Panel
&lt;/h2&gt;

&lt;p&gt;I started with a minimal set of variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.shape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--translate-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--translate-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--translate-z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="py"&gt;--rotate-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--rotate-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--rotate-z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="py"&gt;--scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&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;And here is the key moment.&lt;/p&gt;

&lt;p&gt;You change these variables on the HTML element — and the 3D object does the exact same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At this point, CSS stops being just styles and becomes an API.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now CSS animations, pseudo-classes, and media queries all become tools for controlling 3D.&lt;/p&gt;




&lt;h2&gt;
  
  
  Models: Yes, GLTF works too
&lt;/h2&gt;

&lt;p&gt;Once you stop playing with cubes, the fun starts.&lt;/p&gt;

&lt;p&gt;Connecting the loader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;String3D&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreeJSProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;gltf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GLTFLoader&lt;/span&gt; &lt;span class="p"&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;stringTune&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StringTune&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;stringTune&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;String3D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;modelLoaderFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GLTFLoader&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;stringTune&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"shape"&lt;/span&gt;
  &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"3d"&lt;/span&gt;
  &lt;span class="na"&gt;string-3d=&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt;
  &lt;span class="na"&gt;string-3d-model=&lt;/span&gt;&lt;span class="s"&gt;"/models/damaged_helmet.glb"&lt;/span&gt;
  &lt;span class="na"&gt;string-3d-model-fit=&lt;/span&gt;&lt;span class="s"&gt;"contain"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fit="contain"&lt;/code&gt; (or &lt;code&gt;cover&lt;/code&gt;) is essentially &lt;code&gt;object-fit&lt;/code&gt;, but for 3D:&lt;br&gt;
the model adapts to the element size, not the other way around.&lt;/p&gt;


&lt;h2&gt;
  
  
  Examples: CSS doing the heavy lifting
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.shape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--rotate-y&lt;/span&gt; &lt;span class="m"&gt;300ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;--scale&lt;/span&gt; &lt;span class="m"&gt;300ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.shape&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--rotate-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.2&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;Or keyframes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--translate-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--rotate-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--rotate-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--translate-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--rotate-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--rotate-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&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;span class="nc"&gt;.shape.is-spinning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="m"&gt;2s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&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;h2&gt;
  
  
  The real meaning: when modules play together
&lt;/h2&gt;

&lt;p&gt;Here is where String3D stops being a &lt;em&gt;demo&lt;/em&gt; and becomes a &lt;em&gt;tool&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I took &lt;strong&gt;StringProgress&lt;/strong&gt; (scroll state) and &lt;strong&gt;StringImpulse&lt;/strong&gt; (interaction / push physics) and tied their CSS variables to the 3D transformations.&lt;/p&gt;

&lt;p&gt;Markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"shape-wrapper"&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"progress"&lt;/span&gt; &lt;span class="na"&gt;string-enter-vp=&lt;/span&gt;&lt;span class="s"&gt;"top"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"shape"&lt;/span&gt;
    &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"3d|impulse"&lt;/span&gt;
    &lt;span class="na"&gt;string-3d=&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt;
    &lt;span class="na"&gt;string-3d-model=&lt;/span&gt;&lt;span class="s"&gt;"/models/damaged_helmet.glb"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"3d"&lt;/span&gt; &lt;span class="na"&gt;string-3d=&lt;/span&gt;&lt;span class="s"&gt;"ambientLight"&lt;/span&gt; &lt;span class="na"&gt;string-3d-color=&lt;/span&gt;&lt;span class="s"&gt;"#ffffff"&lt;/span&gt; &lt;span class="na"&gt;string-3d-intensity=&lt;/span&gt;&lt;span class="s"&gt;"3.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;string=&lt;/span&gt;&lt;span class="s"&gt;"3d"&lt;/span&gt; &lt;span class="na"&gt;string-3d=&lt;/span&gt;&lt;span class="s"&gt;"directionalLight"&lt;/span&gt; &lt;span class="na"&gt;string-3d-color=&lt;/span&gt;&lt;span class="s"&gt;"#ffffff"&lt;/span&gt; &lt;span class="na"&gt;string-3d-intensity=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.shape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Scroll drives rotation and X movement */&lt;/span&gt;
  &lt;span class="py"&gt;--translate-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;360&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--push-x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="py"&gt;--rotate-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;310&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c"&gt;/* Mouse adds impulse */&lt;/span&gt;
  &lt;span class="py"&gt;--translate-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--push-y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--rotate-z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--push-rotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c"&gt;/* Scale is also tied to scroll */&lt;/span&gt;
  &lt;span class="py"&gt;--scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--progress&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;JS doesn’t drive the animation directly.&lt;br&gt;
It only provides state (&lt;code&gt;--progress&lt;/code&gt;, &lt;code&gt;--push-x&lt;/code&gt;, &lt;code&gt;--push-rotation&lt;/code&gt;) — produced by StringTune’s core modules like scroll and interaction tracking.&lt;/p&gt;

&lt;p&gt;The entire logic of &lt;em&gt;how it looks&lt;/em&gt; lives in CSS.&lt;br&gt;
And that is the whole idea.&lt;/p&gt;


&lt;h2&gt;
  
  
  How it works (no mythology)
&lt;/h2&gt;

&lt;p&gt;If we strip it down to the verifiable basics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The module scans the DOM and finds elements with &lt;code&gt;string="3d"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For each element, it creates a corresponding 3D object (mesh, light, group, or model).&lt;/li&gt;
&lt;li&gt;A canvas overlay renders the scene above the viewport.&lt;/li&gt;
&lt;li&gt;Every frame:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Reads &lt;code&gt;getBoundingClientRect()&lt;/code&gt; and computed styles&lt;/li&gt;
&lt;li&gt;Extracts CSS vars (&lt;code&gt;--translate-*&lt;/code&gt;, &lt;code&gt;--rotate-*&lt;/code&gt;, &lt;code&gt;--scale&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Converts DOM coords to world coords via the camera&lt;/li&gt;
&lt;li&gt;Sets position, rotation, and scale&lt;/li&gt;
&lt;li&gt;Renders the scene&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;DOM is the source of truth.&lt;br&gt;
3D is a shadow that obeys CSS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, an orthographic camera is used, so the binding feels like&lt;br&gt;
&lt;strong&gt;“1px ≈ 1 unit”&lt;/strong&gt;, without the circus of perspective distortion.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why did I even get into this?
&lt;/h2&gt;

&lt;p&gt;Because I got tired of the same old scenario.&lt;/p&gt;

&lt;p&gt;You build a layout in HTML.&lt;br&gt;
Then you add Three.js.&lt;br&gt;
Then it begins: &lt;em&gt;“now sync this,” “now resize,” “now make it sticky,” “now handle another breakpoint.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And in the end, you have either two worlds constantly fighting each other,&lt;br&gt;
or a massive pile of glue code that is scary to touch.&lt;/p&gt;

&lt;p&gt;I want a different mode:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML builds the world.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;CSS describes the behavior.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;3D connects as a layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right now, this code is a mix of heuristics, compromises, and pure enthusiasm.&lt;br&gt;
There are places in the source I prefer not to look at too often, and I know for a fact some parts will need a rewrite.&lt;/p&gt;

&lt;p&gt;Logically, it shouldn’t work like this.&lt;br&gt;
We’re tricking the system, forcing the DOM to think it controls something it doesn’t even know exists.&lt;br&gt;
It’s a hack.&lt;/p&gt;

&lt;p&gt;But when I write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;transition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;--rotate-y&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;save the file, and watch a heavy 3D model rotate smoothly in the browser&lt;br&gt;
&lt;strong&gt;without a single line of JS&lt;/strong&gt; — I get a strange feeling.&lt;/p&gt;

&lt;p&gt;It feels like finding that one missing bit the web has been lacking all these years.&lt;/p&gt;

&lt;p&gt;Maybe it’s an illusion.&lt;br&gt;
Or maybe this is how it was supposed to be from the start.&lt;/p&gt;

&lt;p&gt;If you’ve built something similar — or tried and failed — I’d love to hear how you approached it.&lt;br&gt;
Especially if you ended up with a pile of glue code.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/string-tune-3d" rel="noopener noreferrer"&gt;[NPM Link]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/penev-palemiya/StringTune-3D" rel="noopener noreferrer"&gt;[GitHub Link]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Assets &amp;amp; Credits&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Damaged Helmet (source .glb)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://sketchfab.com/3d-models/damaged-helmet-source-glb-fc8449ca5f3f490d83ba6a7ae087ff55" rel="noopener noreferrer"&gt;https://sketchfab.com/3d-models/damaged-helmet-source-glb-fc8449ca5f3f490d83ba6a7ae087ff55&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Licensed under &lt;strong&gt;CC-BY 4.0&lt;/strong&gt; (via Sketchfab)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;If it breaks — great.&lt;/strong&gt;&lt;br&gt;
Send me what broke, where, and in which browser.&lt;br&gt;
That’s exactly what I’m hunting for.&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
