<?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: Johnson Fash</title>
    <description>The latest articles on DEV Community by Johnson Fash (@johnsonfash).</description>
    <link>https://dev.to/johnsonfash</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F618913%2F523c9246-8eb5-463b-b193-4ebdf68a5c3f.png</url>
      <title>DEV Community: Johnson Fash</title>
      <link>https://dev.to/johnsonfash</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnsonfash"/>
    <language>en</language>
    <item>
      <title>One query language for SQL, Mongo, and the browser: the case for Forge</title>
      <dc:creator>Johnson Fash</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:28:35 +0000</pubDate>
      <link>https://dev.to/johnsonfash/one-query-language-for-sql-mongo-and-the-browser-the-case-for-forge-247o</link>
      <guid>https://dev.to/johnsonfash/one-query-language-for-sql-mongo-and-the-browser-the-case-for-forge-247o</guid>
      <description>&lt;p&gt;There's a moment in most TypeScript backend projects when the data layer stops being one thing.&lt;/p&gt;

&lt;p&gt;Maybe you started on Postgres, then a feature shipped where audit events made more sense as a flat Mongo document. Maybe your mobile app needs to work on the train, so you added SQLite in the browser. Maybe a "find me the three nearest locations" endpoint forced you to drop into raw ST_Distance_Sphere() because your ORM didn't know what a geo point was. Maybe you added embeddings for an AI search box and now half your query path is a $queryRaw with a hand-rolled vector cosine clause.&lt;/p&gt;

&lt;p&gt;Each of those moments seems small in isolation. Stacked, they're how a typed data layer becomes four data layers — a Prisma client over here, a Mongoose schema over there, Dexie wrapping IndexedDB, and a folder full of raw SQL strings nobody wants to touch. Each layer has its own concept of "model," its own migration story, its own way of describing a where clause. The code in your services is suddenly half plumbing.&lt;/p&gt;

&lt;p&gt;I built Forge because every other TypeScript ORM I tried forced me to pick one surface and treat the rest as a foreign country.&lt;/p&gt;




&lt;p&gt;(&lt;a href="https://www.npmjs.com/package/forge-orm" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/forge-orm&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that produced it
&lt;/h2&gt;

&lt;p&gt;I was working on a multi-tenant SaaS that had to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run Postgres for transactional state&lt;/li&gt;
&lt;li&gt;Use Mongo for high-volume nested documents that didn't want a schema&lt;/li&gt;
&lt;li&gt;Work fully offline in the browser on a Tauri-packaged client, including a point-of-sale flow that took payments without a network&lt;/li&gt;
&lt;li&gt;Geocode every location and answer nearest-neighbor queries on them&lt;/li&gt;
&lt;li&gt;Eventually add embeddings so an operator could type "blue cotton tee" and find products without exact keyword matches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick any two of those and the existing ORMs work. Pick three and you start writing glue. Pick all five and you spend more time keeping four data layers in sync than building features.&lt;/p&gt;

&lt;p&gt;Prisma covers Postgres beautifully and treats Mongo as a second-class adapter. It has no browser story at all. Geo is raw SQL. Vector is raw SQL.&lt;/p&gt;

&lt;p&gt;Drizzle is the cleanest TS-native SQL builder I've ever used — but it's SQL-only. No Mongo. No browser. Geo and vector escape into hand-written fragments.&lt;/p&gt;

&lt;p&gt;Kysely has the best types of any query builder, but it's also SQL-only and intentionally not an ORM. You write joins. No relations sugar. No browser.&lt;/p&gt;

&lt;p&gt;TypeORM has Mongo support, but the types degrade to any so often the TypeScript story is barely real, and decorators don't compose with edge runtimes.&lt;/p&gt;

&lt;p&gt;Mongoose handles Mongo well. It does not handle Postgres at all.&lt;/p&gt;

&lt;p&gt;Dexie handles IndexedDB. It does not handle anything else.&lt;/p&gt;

&lt;p&gt;So you stack them. And you write a small in-house abstraction layer on top so service code doesn't have to know which one it's hitting. And that layer eats a month every year just keeping up.&lt;/p&gt;

&lt;p&gt;Forge is what happens when you decide that's not acceptable and try to put one query language under all of it.&lt;/p&gt;




&lt;p&gt;Video: &lt;a href="https://youtu.be/FpOaneoCzpw" rel="noopener noreferrer"&gt;https://youtu.be/FpOaneoCzpw&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually get
&lt;/h2&gt;

&lt;p&gt;*&lt;em&gt;One vocabulary across six dialects&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
Forge has adapters for Postgres, MySQL, SQLite, Mongo, DuckDB, and MSSQL. The same query method names work on all six:&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="c1"&gt;// Postgres&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Mongo&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// DuckDB (analytical)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not "compatible-ish." Those are byte-identical service-layer calls. The adapter underneath translates where, limit, orderBy, select, include, groupBy, upsert, transaction, and the rest into the dialect's native form. A Mongo $or becomes a SQL OR. A SQL NOT IN becomes Mongo's $nin. JSON path access becomes -&amp;gt;&amp;gt; on Postgres and dotted-key access on Mongo.&lt;/p&gt;

&lt;p&gt;The point isn't that you'll switch databases (you mostly won't). The point is that when a feature lands that needs the other database, you don't relearn an ORM. You point a different db at the same schema and write the same code.&lt;/p&gt;

&lt;p&gt;The browser is a first-class target&lt;/p&gt;

&lt;p&gt;This is the part I haven't seen elsewhere.&lt;/p&gt;

&lt;p&gt;Forge ships a browser adapter built on sqlite-wasm + OPFS, running in a Web Worker. It mounts as opfs:/your-db.sqlite or opfs-sahpool:/... for synchronous-access-handle mode, or :memory: for tests. There are Vite, Next.js, and Webpack plugins that handle the wasm loading without you wiring it.&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="c1"&gt;// In the browser. Same API as the server.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createBrowserDb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opfs:/dallio.sqlite&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INV-3308&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4250&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;drafts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="dl"&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 query you wrote for your server's Postgres works in the user's browser against SQLite. You stop maintaining two data layers because the same model is the same model. Offline drafts, offline POS sales, offline product catalogs — all queryable with the same findMany your service code uses, no IndexedDB wrapper, no Dexie schema duplicated from your Postgres schema.&lt;/p&gt;

&lt;p&gt;For a Tauri app or a PWA that needs to keep working when the network drops, this collapses an entire architectural sub-project into a config flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Geo, vector, and JSON path are typed
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Place&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;places&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geoPoint&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;srid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;openingHours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Nearest 10 places to a point — Postgres uses PostGIS, MySQL uses&lt;/span&gt;
&lt;span class="c1"&gt;// ST_Distance_Sphere, Mongo uses $near. Same query call.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nearby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Place&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;nearTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;3.42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.45&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;withinMeters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&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="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Vector similarity — same vocabulary as geo&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;similar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Place&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;nearTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Typed JSON path — no string fragments&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cafes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Place&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&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;details.tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cafe&lt;/span&gt;&lt;span class="dl"&gt;"&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;None of those require raw SQL. None require a separate vector library. None require a PostGIS adapter package. The results are typed — nearby[0].location is a [number, number], similar[0].embedding is number[], details.tags is string[].&lt;/p&gt;

&lt;p&gt;If you've ever written a cosine_similarity() SQL fragment by hand or tried to thread PostGIS through Prisma raw queries, you know what the absence of this costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  No binary engine, no codegen step
&lt;/h2&gt;

&lt;p&gt;Forge is pure TypeScript. There is no Rust query engine that needs to be downloaded per platform, no prisma generate step in your CI, no .prisma schema file written in a custom DSL.&lt;/p&gt;

&lt;p&gt;Your schema is a TS file. Models are values. Importing them gives you typed query methods. That's the whole setup.&lt;/p&gt;

&lt;p&gt;Cold start on Lambda or Vercel Functions is under 50ms, the same as Drizzle and Kysely, an order of magnitude faster than Prisma's historical baseline. Edge runtimes (Workers, Vercel Edge, Bun, Deno) work natively because nothing about Forge requires Node-specific APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift detection that works in production
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$migrate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   alteredColumns: [{ table: "users", column: "phone", action: "added" }],&lt;/span&gt;
&lt;span class="c1"&gt;//   pending: [{ table: "users", column: "legacy_id", action: "drop" }],&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;db.$migrate()&lt;/code&gt; walks the live schema against the model definitions. Safe drift (an added column, a new index) gets applied automatically. Destructive drift (a column that no longer exists in the model) is surfaced under pending for your migration tool to handle deliberately. You can opt out with db.$migrate({ alter: false }).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;db.$diff(driver, { schema })&lt;/code&gt; returns the same report without applying anything. db.$doctor() runs a live probe — checks extensions, indexes, encoding, latency — and tells you what's wrong. Both work in the browser too, against the user's OPFS SQLite.&lt;/p&gt;

&lt;p&gt;In practice this means the day a new feature ships with two new columns, you don't need to ship a migration file. You can let $migrate() apply the additions on boot and ship a migration for the destructive part next sprint. Or, if you're more conservative, you can run $diff() in CI and refuse to deploy when drift is detected. Both flows work.&lt;/p&gt;

&lt;p&gt;Atomic upsert that actually is atomic&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scopedDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lastSeenAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&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;That call compiles to INSERT ... ON CONFLICT DO UPDATE on Postgres, INSERT ... ON DUPLICATE KEY UPDATE on MySQL, MERGE on MSSQL, &lt;/p&gt;

&lt;p&gt;&lt;code&gt;findOneAndUpdate({ upsert: true })&lt;/code&gt; on Mongo, and the SQLite equivalent. One round trip. No find-then-branch race condition. No accidental double-insert under concurrency.&lt;/p&gt;

&lt;p&gt;I mention this because it's not glamorous, but every ORM that doesn't do it correctly produces a class of intermittent production bugs you can never reproduce locally and your tracing tool blames on "user double-clicked." Forge does it correctly across all six dialects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Forge is not
&lt;/h2&gt;

&lt;p&gt;I'll be direct about this because honest tradeoffs are part of why anyone trusts a recommendation.&lt;/p&gt;

&lt;p&gt;There is no Studio. Prisma Studio is a category-defining piece of tooling. db.$doctor is CLI-only and doesn't browse data. If your team includes people who want to click around tables in a GUI, Forge will frustrate them until a Studio ships.&lt;/p&gt;

&lt;p&gt;There is no SaaS company behind it. That has good and bad sides. No paid Accelerate/Pulse/Optimize tier means the OSS roadmap isn't shaped by upsell pressure. No SaaS company also means no support contract, no enterprise SLA, no Salesforce conference booth, no team of dedicated maintainers if I get hit by a bus.&lt;/p&gt;

&lt;p&gt;The community is tiny. If you Google a Forge error, you'll mostly find the README. Stack Overflow won't help. There are no YouTube tutorials. Onboarding a junior engineer takes longer because the answer to "how do I do X" is "read the source," not "watch this 20-minute video."&lt;/p&gt;

&lt;p&gt;Depth versus breadth. Drizzle has gone deeper into Postgres-specific edge cases than Forge has. Mongoose has gone deeper into Mongo's aggregation pipeline. Forge covers six dialects well, but a dialect-specific ORM will always go deeper into its one dialect. If you need Postgres logical replication consumer slots or Mongo's exact change-stream resume semantics surfaced as a first-class API, you'll write that integration yourself in Forge.&lt;/p&gt;

&lt;p&gt;It's young. First release was 2024. 2.5.3 is the current version. The shape is stable, the test suite is large (the test file count crossed 470 in 2.5.3, mostly per-dialect parity), but five-year battle scars accumulate things you can't get faster. Expect a few rough edges.&lt;/p&gt;

&lt;p&gt;If those are dealbreakers, Drizzle or Prisma is your answer. That's a real choice, not a consolation prize.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Forge is the right answer
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Pick Forge when:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your data already lives in more than one place. Postgres + Mongo. Postgres + SQLite (mobile sync). DuckDB for analytics over a transactional Postgres. MSSQL for a legacy system plus Postgres for the new one. Forge collapses the abstraction tax of running multiple ORMs.&lt;/li&gt;
&lt;li&gt;You're building an offline-first or local-first app. Tauri desktop apps, Capacitor mobile apps, PWAs that need to work on flaky connections. Forge's browser adapter means you maintain one data layer instead of one server data layer plus one client IndexedDB layer plus the sync code between them.&lt;/li&gt;
&lt;li&gt;Geo or vector is on your roadmap. Even if you don't need them today, building them on raw SQL fragments is a permanent tax. Forge surfaces both as typed primitives.&lt;/li&gt;
&lt;li&gt;You care about cold starts. Lambda, Vercel Functions, Cloudflare Workers — Forge is pure TS, no binary, no codegen runtime. Drizzle and Kysely match it; Prisma still doesn't.&lt;/li&gt;
&lt;li&gt;You like writing code more than configuration. The schema is a TS file. The migration story is "the live schema and the models disagree, here's what changed." There's no DSL, no codegen step, no separate migration CLI to learn.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Don't pick Forge when:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You have a senior Postgres-only team that already loves Drizzle. Forge wouldn't make their day better.&lt;/li&gt;
&lt;li&gt;You need Studio and your stakeholders aren't going to budge.&lt;/li&gt;
&lt;li&gt;You're shipping inside a company that requires a vendor with a support contract.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npm install forge-orm@^2.5.3&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Define a model file:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// schema.ts&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;f&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;forge-orm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free&lt;/span&gt;&lt;span class="dl"&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;pro&lt;/span&gt;&lt;span class="dl"&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;enterprise&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;defaultNow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Connect from your server:
&lt;/h2&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;createPostgresDb&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;forge-orm/postgres&lt;/span&gt;&lt;span class="dl"&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;schema&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;./schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPostgresDb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$migrate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// applies safe drift on boot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or from the browser:&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;createBrowserDb&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;forge-orm/browser&lt;/span&gt;&lt;span class="dl"&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;schema&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;./schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createBrowserDb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opfs:/app.sqlite&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$migrate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same db.User.findMany({ where: { tier: "pro" } }) works in both. That is the entire setup.&lt;/p&gt;




&lt;p&gt;A statement about TypeScript data layers in 2026&lt;/p&gt;

&lt;p&gt;The data layer should follow your data, not the other way around. When a feature pushes you toward a different database — analytical, document, offline — the ORM should come with you, not force you to maintain a second one.&lt;/p&gt;

&lt;p&gt;Most TypeScript ORMs were built when "the database" meant Postgres or maybe MySQL. They optimize for the depth of that single relationship. That's an honest tradeoff and it produces excellent tools for the case where it's correct.&lt;/p&gt;

&lt;p&gt;But TypeScript apps in 2026 aren't single-database anymore. They run partially in the browser. They store transactional data in one engine and analytical data in another. They pull embeddings out of vector indexes and locations out of geo indexes. They sync some state offline and reconcile it when the network returns.&lt;/p&gt;

&lt;p&gt;Forge is what an ORM looks like if you take that reality as the starting point.&lt;/p&gt;

&lt;p&gt;It is not the safe choice. The safe choice is Prisma. The cool choice is Drizzle. The purist choice is Kysely.&lt;/p&gt;

&lt;p&gt;Forge is the choice when none of those covers your data's actual shape, and you'd rather pay the cost of a younger tool than the cost of running four mature ones in parallel for the rest of the project's life.&lt;/p&gt;

&lt;p&gt;If that's where you are, forge-orm@^2.5.3 (&lt;a href="https://www.npmjs.com/package/forge-orm" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/forge-orm&lt;/a&gt;) is one npm install away.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>sql</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How I Built a Chrome Extension to Extract Emails and Phone Numbers – A Developer’s Journey</title>
      <dc:creator>Johnson Fash</dc:creator>
      <pubDate>Sat, 29 Nov 2025 08:55:17 +0000</pubDate>
      <link>https://dev.to/johnsonfash/how-i-built-a-chrome-extension-to-extract-emails-and-phone-numbers-a-developers-journey-5f6a</link>
      <guid>https://dev.to/johnsonfash/how-i-built-a-chrome-extension-to-extract-emails-and-phone-numbers-a-developers-journey-5f6a</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%2F4zrcfttbqnqnw0zylv30.jpg" 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%2F4zrcfttbqnqnw0zylv30.jpg" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&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%2Fj8we7rf5aacpg6alhc7k.jpg" 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%2Fj8we7rf5aacpg6alhc7k.jpg" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&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%2F7xfi483xbhessinxcc8i.jpg" 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%2F7xfi483xbhessinxcc8i.jpg" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a fullstack developer, I’m always on the lookout for tools that save time and automate repetitive tasks. One thing I noticed early in my career is that collecting contact information—emails and phone numbers—from websites can be tedious, especially if you’re doing it manually. Whether it’s for outreach, research, or building a curated contact list, manually scanning pages is slow, error-prone, and frankly, exhausting.&lt;/p&gt;

&lt;p&gt;That’s when I decided to create my very first Chrome extension: an Email and Phone Extractor. This project wasn’t just about scraping data; it was about building a lightweight, user-friendly, and reliable tool that anyone could use ethically and efficiently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/email-phone-extractor/oflljapdhcnlfapfammamnieoknpfhfj" rel="noopener noreferrer"&gt;You can add the Extension to your chrome browser here!&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This Extension
&lt;/h2&gt;

&lt;p&gt;The idea came from my own workflow frustrations. I often needed contact information from company websites, portfolio pages, or public directories. Copying and pasting each email or number was draining. I wanted a tool that would scan the page automatically, organize the results, and give me a clean output I could use immediately.&lt;/p&gt;

&lt;p&gt;I envisioned a simple Chrome extension that could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect all email addresses and phone numbers on the current page.&lt;/li&gt;
&lt;li&gt;Provide a clear interface to view, copy, or save the extracted contacts.&lt;/li&gt;
&lt;li&gt;Handle dynamically loaded content without slowing down the browser.&lt;/li&gt;
&lt;li&gt;Be easy for anyone to install and use, even without a technical background.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started with Chrome Extension Development
&lt;/h2&gt;

&lt;p&gt;Building a Chrome extension was a new adventure for me. I started by diving into Chrome’s extension APIs. Chrome extensions have three main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Manifest file – defines the extension’s metadata and permissions.&lt;/li&gt;
&lt;li&gt;Content scripts – allow the extension to interact with the web page’s DOM.&lt;/li&gt;
&lt;li&gt;Background scripts – handle messaging, persistent storage, and long-running tasks.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Building a Clean and Intuitive Interface
&lt;/h2&gt;

&lt;p&gt;One of my main goals was user experience. I wanted the extension to be simple but functional. The popup displays:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Emails and phone numbers in separate sections&lt;/li&gt;
&lt;li&gt;A button to copy all results to the clipboard&lt;/li&gt;
&lt;li&gt;A count of unique emails and phone numbers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Optional filters to remove duplicates&lt;/p&gt;

&lt;p&gt;I wanted users to feel in control. They can extract all contacts with a single click and copy them without dealing with messy page content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ethical Considerations
&lt;/h2&gt;

&lt;p&gt;While the extension can extract public information, I wanted to make ethical use clear. I added a disclaimer reminding users to respect privacy laws and not misuse the tool for spam. Emails and phone numbers collected should only be used for legitimate purposes, like reaching out professionally or conducting research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges Along the Way
&lt;/h2&gt;

&lt;p&gt;Building this extension wasn’t without hurdles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Handling large pages: Some websites have thousands of DOM nodes. I had to ensure the extension scanned efficiently without freezing the browser.&lt;/li&gt;
&lt;li&gt;Dynamic content: Modern sites often load data asynchronously. Mutation observers were critical here.&lt;/li&gt;
&lt;li&gt;False positives: Regex sometimes matched text that looked like emails or phone numbers but weren’t. I added filtering and validation to minimize this.&lt;/li&gt;
&lt;li&gt;Cross-browser quirks: While building primarily for Chrome, I ensured that the extension’s logic could be adapted for other Chromium-based browsers like Edge and Brave.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real-Life Use Cases
&lt;/h2&gt;

&lt;p&gt;Here are some ways developers and professionals can benefit from this extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sales and Business Development: Quickly gather leads from public directories and websites.&lt;/li&gt;
&lt;li&gt;Recruitment: Extract contact information of potential candidates from company pages or LinkedIn profiles (respecting platform policies).&lt;/li&gt;
&lt;li&gt;Networking: Build professional connections by compiling contact lists for outreach.&lt;/li&gt;
&lt;li&gt;Research &amp;amp; Data Collection: Academics and researchers can collect emails for surveys or collaborations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because of these use cases, the extension isn’t just a productivity tool—it’s a way to automate routine tasks and focus on what truly matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing My Work
&lt;/h2&gt;

&lt;p&gt;After testing extensively and refining the features, I published the extension on the Chrome Web Store. The process taught me a lot about packaging, metadata, and compliance with Google’s publishing rules. I also shared it with friends and colleagues, who found it immediately useful.&lt;/p&gt;

&lt;p&gt;For those interested, you can try it here: Email &amp;amp; Phone Extractor Chrome Extension&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building this Chrome extension was more than a coding exercise—it was a lesson in user experience, problem-solving, and ethical development. Some key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Even small projects can have complex technical challenges&lt;/li&gt;
&lt;li&gt;Regex is powerful but requires careful testing&lt;/li&gt;
&lt;li&gt;Handling dynamic content is critical for modern web tools&lt;/li&gt;
&lt;li&gt;Ethical considerations should never be overlooked&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Creating the Email and Phone Extractor Chrome extension was a fulfilling journey. It started as a personal tool to save time and evolved into a polished extension that can help other developers, researchers, and professionals streamline their workflows.&lt;/p&gt;

&lt;p&gt;If you’re a developer looking for a practical project, Chrome extension development is a fantastic way to combine front-end skills with practical problem-solving. And who knows—you might just build a tool that thousands of people end up using!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Email &amp; Phone Extractor — One-Click Contact Collection for Any Website</title>
      <dc:creator>Johnson Fash</dc:creator>
      <pubDate>Tue, 25 Nov 2025 10:04:03 +0000</pubDate>
      <link>https://dev.to/johnsonfash/email-phone-extractor-one-click-contact-collection-for-any-website-4f8d</link>
      <guid>https://dev.to/johnsonfash/email-phone-extractor-one-click-contact-collection-for-any-website-4f8d</guid>
      <description>&lt;p&gt;A friend of mine spent hours manually collecting emails and phone numbers from websites. If you have, then you know it can drive one insane.&lt;/p&gt;

&lt;p&gt;After watching a friend on a google meet “speed-run” through LinkedIn, and opened a couple of company websites for lead generation, I realized something: the tools that automate this were either too expensive or too limited.&lt;/p&gt;

&lt;p&gt;So, as a developer, i built a solution — a Chrome extension that extracts emails and phone numbers from any webpage with just one click.&lt;/p&gt;

&lt;p&gt;Here’s the story behind it, how it works, and why it could change the way you handle web-based contacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Story Behind Email &amp;amp; Phone Extractor
&lt;/h2&gt;

&lt;p&gt;About two months ago, a friend of mine, a digital marketer, was giving me tips on LinkedIn optimization.&lt;/p&gt;

&lt;p&gt;While talking, he opened Google Maps, lists of companies, multiple website tabs, and internal links — basically navigating a maze while multitasking. I asked him,&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“Bro, why is this so manual?”&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
He laughed and said:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“The extensions that automate this are too expensive for Nigerians.”&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
That one sentence sparked an idea. I love turning repetitive, manual tasks into clean, one-click solutions. That night, I started building the first version of Email &amp;amp; Phone Extractor, and now it’s a real Chrome extension that works today.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Install the Extension&lt;br&gt;
Available on the Chrome Web Store. One click installs it in seconds.&lt;/p&gt;

&lt;p&gt;Navigate to Any Page&lt;br&gt;
Works on LinkedIn, WhatsApp Web, company websites, and almost any Single Page Application (SPA).&lt;/p&gt;

&lt;p&gt;Click the Extension Icon&lt;br&gt;
Instantly extracts emails and phone numbers visible on the page, including content loaded dynamically via JavaScript.&lt;/p&gt;

&lt;p&gt;View or Export Data&lt;br&gt;
Export contacts as JSON or copy to clipboard. Manage them on your local machine or via our optional account sync feature.&lt;/p&gt;

&lt;p&gt;Handles Complex Sites&lt;br&gt;
Unlike simple scrapers, this tool digs into local and session storage, IndexedDB, and cached data to extract contacts even when they’re hidden from the DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;The mission is simple: save time, reduce frustration, and make web contact collection accessible to everyone.&lt;/p&gt;

&lt;p&gt;Whether you’re a marketer, sales professional, researcher, or just curious, this extension turns tedious manual scraping into a clean, one-click workflow.&lt;/p&gt;

&lt;p&gt;Speed: Collect data in seconds, not hours.&lt;/p&gt;

&lt;p&gt;Versatility: Works on almost any site.&lt;/p&gt;

&lt;p&gt;Accessibility: No expensive subscriptions or complex setups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo Video
&lt;/h2&gt;

&lt;p&gt;Watch it in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/GWFwXpZ9Tl4" rel="noopener noreferrer"&gt;https://youtu.be/GWFwXpZ9Tl4&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&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%2F40jkck36497s8x5erytt.jpg" 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%2F40jkck36497s8x5erytt.jpg" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&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%2F3uyn76b8vezrjihg17ax.jpg" 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%2F3uyn76b8vezrjihg17ax.jpg" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&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%2F1rvwfqcb91wpdsj1i7yh.jpg" 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%2F1rvwfqcb91wpdsj1i7yh.jpg" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&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%2Fttcdlwt4xlh3rzfexgp3.jpg" 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%2Fttcdlwt4xlh3rzfexgp3.jpg" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Install the Chrome extension, and start extracting emails and phone numbers in one click:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/email-phone-extractor/oflljapdhcnlfapfammamnieoknpfhfj" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://contact-extractor.tdigital.ng" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>extensions</category>
    </item>
    <item>
      <title>Let Me Introduce to You My Portfolio Website</title>
      <dc:creator>Johnson Fash</dc:creator>
      <pubDate>Mon, 26 Apr 2021 14:45:59 +0000</pubDate>
      <link>https://dev.to/johnsonfash/let-me-introduce-to-you-my-portfolio-website-4p52</link>
      <guid>https://dev.to/johnsonfash/let-me-introduce-to-you-my-portfolio-website-4p52</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Hello Everyone,&amp;nbsp;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I have finally &lt;strong&gt;completed&lt;/strong&gt; the built of my &lt;strong&gt;portfolio website&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Features&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Static Landing Page with CSS,HTML and JavaScript Custom Animation&lt;/li&gt;
&lt;li&gt; Blog Posts&lt;/li&gt;
&lt;li&gt; Custom Management System (WordPress Clone) with SEO Manager&lt;/li&gt;
&lt;li&gt; Project Demo and more&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the Link: &lt;a href="https://apluswebmaker.com"&gt;https://apluswebmaker.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portfolio</category>
      <category>website</category>
    </item>
    <item>
      <title>JavaScript On Scroll Animation Library</title>
      <dc:creator>Johnson Fash</dc:creator>
      <pubDate>Thu, 22 Apr 2021 07:51:59 +0000</pubDate>
      <link>https://dev.to/johnsonfash/javascript-on-scroll-animation-library-3jh9</link>
      <guid>https://dev.to/johnsonfash/javascript-on-scroll-animation-library-3jh9</guid>
      <description>&lt;h1&gt;
  
  
  &lt;strong&gt;OnScroll Animation&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://travis-ci.com/johnsonfash/scroll-animation"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m1WgLos8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://travis-ci.com/johnsonfash/scroll-animation.svg%3Fbranch%3Dmaster" alt="Build Status"&gt;&lt;/a&gt; &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ES67nC1M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/github/package-json/v/johnsonfash/scroll-animation" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ES67nC1M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/github/package-json/v/johnsonfash/scroll-animation" alt="GitHub package.json version"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GQFBwHZi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/twitter/follow/iamJohnsonFash%3Fstyle%3Dsocial" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GQFBwHZi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/twitter/follow/iamJohnsonFash%3Fstyle%3Dsocial" alt="Twitter Follow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Scroll Animation Library is a simple JavaScript library for animation when element(s) are in view while scrolling the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 &lt;strong&gt;&lt;a href="https://johnsonfash.github.io/onscroll-animation/website.html"&gt;Demo&lt;/a&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://johnsonfash.github.io/onscroll-animation/website.html"&gt;Custom website build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://johnsonfash.github.io/onscroll-animation/3d-box.html"&gt;3D box animation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://johnsonfash.github.io/onscroll-animation/articles.html"&gt;Article Slides&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  ⚙ &lt;strong&gt;Installation&lt;/strong&gt;
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Option A.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;NPM installation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;npm install onscroll-animation --save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Import:&lt;/strong&gt;&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&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;onscroll-animation&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;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&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;h3&gt;
  
  
  &lt;strong&gt;Option B.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use CDN - load directly from&amp;nbsp;jsDelivr CDN&lt;/strong&gt;&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;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/onscroll-animation@latest/dist/animate.bundle.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  &lt;strong&gt;Use&lt;/strong&gt;
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.grid11&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&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;animation-duration: 0.8s&lt;/span&gt;&lt;span class="dl"&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;animation-delay: 1s&lt;/span&gt;&lt;span class="dl"&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;animation-fill-mode: forwards&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transform: translateX(-150px)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;section.one .left, section.three .book, section.five .other&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left: -500px&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&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;section.one .right, section.three .complex, section.five .person&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right: -500px&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right: 0px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;section.two&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opacity: 0&lt;/span&gt;&lt;span class="dl"&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;transform: translateY(100px)&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opacity: 1&lt;/span&gt;&lt;span class="dl"&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;transform: translateY(0px)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;.grid10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&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;animation-duration: 0.8s&lt;/span&gt;&lt;span class="dl"&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;animation-fill-mode: forwards&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transform: translateY(-110px)&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultParam&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;animation-duration: .8s&lt;/span&gt;&lt;span class="dl"&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;run: once&lt;/span&gt;&lt;span class="dl"&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;animation-fill-mode: forwards&lt;/span&gt;&lt;span class="dl"&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;pixel-correction: -100px&lt;/span&gt;&lt;span class="dl"&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;animation-time-function: ease-out&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  &lt;strong&gt;Explanation&lt;/strong&gt;
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;OnScrollAnimation&amp;nbsp;class&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;OnScrollAnimation({....})&lt;/code&gt;&lt;/strong&gt; accept only an object &lt;strong&gt;&lt;code&gt;{...}&lt;/code&gt;&lt;/strong&gt;. This object contains css selectors like &lt;strong&gt;&lt;code&gt;".grid10"&lt;/code&gt;&lt;/strong&gt;,&amp;nbsp;&lt;strong&gt;&lt;code&gt;"section.two img, section.four img"&lt;/code&gt;&lt;/strong&gt; etc.&lt;/p&gt;

&lt;p&gt;Basically, this object properties can be any css selector, which a &lt;strong&gt;&lt;code&gt;document.querySelector()&lt;/code&gt;&lt;/strong&gt; method accepts.&lt;/p&gt;

&lt;p&gt;The value for the &lt;strong&gt;CSS Selector&lt;/strong&gt; i.e &lt;strong&gt;&lt;code&gt;".grid4"&lt;/code&gt;&lt;/strong&gt; must be an object which holds various &lt;strong&gt;properties&lt;/strong&gt; and &lt;strong&gt;values&lt;/strong&gt; for animation to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Properties&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. `&lt;/strong&gt;parameters:[...]&lt;strong&gt;&lt;code&gt;&amp;nbsp; or &lt;/code&gt;parameters: {...}` ;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This define &lt;strong&gt;&lt;code&gt;@keyframes&lt;/code&gt;&lt;/strong&gt; property for each element i.e&amp;nbsp;&lt;code&gt;parameters: [...]&lt;/code&gt;&amp;nbsp;or&amp;nbsp;&lt;code&gt;parameters: {...)&lt;/code&gt; can be an array containing strings of regular css or object containing its javascript equivalent like the example below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;run&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;run&lt;/code&gt; can be omitted or included. This lets you determine if animation runs &lt;code&gt;once&lt;/code&gt; or continous anytime an animated element is in view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pixelCorrection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pixel-correction&lt;/code&gt; or &lt;code&gt;pixelCorrection&lt;/code&gt; use to make correction (in pixel) to when animation starts for an element. i.e &lt;code&gt;100px&lt;/code&gt; means scroll &lt;code&gt;100px&lt;/code&gt; downward before animation starts for an element in viewport, and &lt;code&gt;-100px&lt;/code&gt; the opposite.&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;parameters&lt;/span&gt;&lt;span class="p"&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;animation-duration: 1s&lt;/span&gt;&lt;span class="dl"&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;animation-delay: 2s&lt;/span&gt;&lt;span class="dl"&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;animation-fill-mode: forwards&lt;/span&gt;&lt;span class="dl"&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;animation-time-function: ease-in&lt;/span&gt;&lt;span class="dl"&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;pixel-correction: -200px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// makes correction to how far down or up to go before element in view animates&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run: once&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;//can be ommited. default is to run everytime element is in view&lt;/span&gt;
    &lt;span class="p"&gt;..........&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;

  &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animationDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;animationDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;animationFillMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;forwards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;animationTimeFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ease-in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;pixelCorrection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-200px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;once&lt;/span&gt;&lt;span class="dl"&gt;"&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;h2&gt;
  
  
  NOTE:
&lt;/h2&gt;

&lt;p&gt;There is non shortcut like &lt;strong&gt;&lt;code&gt;"animation: drop 1s forwards"&lt;/code&gt;&lt;/strong&gt; for now. Please specifically list out your @keyframes by name and function like in the example above.&lt;/p&gt;

&lt;p&gt;Properties of a selector i.e &lt;strong&gt;&lt;code&gt;parameters&lt;/code&gt;&lt;/strong&gt;, &lt;code&gt;**from**&lt;/code&gt;, &lt;strong&gt;&lt;code&gt;to&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;0%&lt;/code&gt;&lt;/strong&gt;, &lt;code&gt;**75%**&lt;/code&gt; and more can both be an array, containing string equivalent of your regular css property or an object containing its equivalent in javascript. i.e &lt;strong&gt;"max-width"&lt;/strong&gt; is &lt;strong&gt;maxWidth&lt;/strong&gt; when working with objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;from: [...]&lt;/code&gt; or &lt;code&gt;from:{...}&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Similar to css property &lt;code&gt;from {.....}&lt;/code&gt; used in &lt;code&gt;@keyframe&lt;/code&gt;. i.e &lt;code&gt;from: ["width: 0px","height:20px"....]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;to: [...]&lt;/code&gt; or &lt;code&gt;to: {....}&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Similar to css property &lt;code&gt;to: {.....}&lt;/code&gt; used in &lt;code&gt;@keyframe&lt;/code&gt; after defining &lt;code&gt;from {...}&lt;/code&gt; i.e &lt;code&gt;to: {width: "100%",height: "200px"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;0: [...], 50: [...], 100:{.....}&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is similar to using percentage in &lt;code&gt;@keyframes&lt;/code&gt;, only difference is not including the &lt;code&gt;%&lt;/code&gt; sign i.e&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#imag1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width: 20px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.....],&lt;/span&gt;
    &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[......],&lt;/span&gt;
    &lt;span class="mi"&gt;80&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="p"&gt;..........&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  &lt;strong&gt;Using custom css&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Without defining animation @keyframes in javascript, custom css can be used with each element by including a &lt;code&gt;class&lt;/code&gt; that defines the &lt;code&gt;@keyframe&lt;/code&gt; in your stylesheet i.e&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;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"ball"&lt;/span&gt; &lt;span class="na"&gt;scr=&lt;/span&gt;&lt;span class="s"&gt;"./asset/ball.jpg"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image1"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.move&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;ballmove&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt; &lt;span class="n"&gt;forwards&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;ballmove&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&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="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.image1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;css&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// adds custom css class only&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;img&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;css&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bounce&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Animation.defaultParams()&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The Animation method &lt;code&gt;defaultParams()&lt;/code&gt; defines a default paramter for each selector. Meaning you can ommit the parameters property for every element if they are all thesame i.e&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.grid1, .grid2&lt;/span&gt;&lt;span class="dl"&gt;"&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="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="p"&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;.grid4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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="p"&gt;[...],&lt;/span&gt;
    &lt;span class="mi"&gt;50&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="p"&gt;........&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultParams&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;animation-duration: 1s&lt;/span&gt;&lt;span class="dl"&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;animation-fill-mode: forwards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// or animation.defaultParams({animationDuration: "2s".......});&lt;/span&gt;
&lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also &lt;strong&gt;ovaride&lt;/strong&gt; the &lt;code&gt;defaultParams()&lt;/code&gt; method for an element by specifying its own &lt;code&gt;parameters&lt;/code&gt; i.e&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OnScrollAnimation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.grid1, .grid2&lt;/span&gt;&lt;span class="dl"&gt;"&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="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="p"&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;.grid4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[....]&lt;/span&gt;  &lt;span class="c1"&gt;// override defaultParams&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt;
    &lt;span class="mi"&gt;50&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="p"&gt;........&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultParams&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;animation-duration: 1s&lt;/span&gt;&lt;span class="dl"&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;animation-fill-mode: forwards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// or animation.defaultParams({animationDuration: "2s".......});&lt;/span&gt;
&lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Animation.init()&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;init()&lt;/code&gt; method initialize the animation to run after the page loads.&lt;/p&gt;

&lt;h1&gt;
  
  
  More Example:
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const animation = new OnScrollAnimation({
        ".one": {
          parameters: [
            "animation-duration: 1s",
            "pixel-correction: -100px",
            "animation-delay: .5s",
            "animation-time-function: linear"
          ],
          from: [
            "transform: translateY(-1000px)"
          ],
          to: [
            "transform: translateY(0px)"
          ]
        },
        ".two": {
          parameters: [
            "animation-duration: 1s",
            "pixel-correction: -300px"
          ],
          from: {
            transform: "rotate(0deg)"
          },
          to: [
            "transform: rotate(360deg)"
          ]
        },
        "article.three": {
          parameters: {
            animationDuration: "1s",
            animationFillMode: "forwards",
            animationTimingFunction: "ease-in"
          },
          0: [
            "transform: translateX(-1000px)",
          ],
          50: [
            "transform: translateX(1000px)",
            "background-color: red"
          ],
          100: [
            "transform: translateX(0px)"
          ]
        },
        ".four": {
          parameters: [
            "animation-duration: 1s"
          ],
          from: [
            "transform: skewX(20deg) translateX(-1000px)"
          ],
          to: [
            "transform: skewX(0deg) translateX(0px)"
          ]
        },
        ".five": {
          parameters: [
            "animation-duration: 1s"
          ],
          from: [
            "position: relative",
            "right: -1000px",
            "transform: skewX(-20deg)"
          ],
          to: [
            "position: relative",
            "right: 0px",
            "transform: skewX(0deg)"
          ]
        },
        ".six": {
          parameters: [
            "animation-duration: 2s",
            "animation-fill-mode: forwards",
          ],
          0: [
            "transform: translateY(0)"
          ],
          75: [
            "transform: translateY(50vh)"
          ]
        },
        ".seven": {
          parameters: [
            "animation-duration: 1.5s"
          ],
          from: [
          "transform: rotateY(0deg)"
          ],
          to: [
          "transform: rotateY(360deg)"
          ]
        }
      });
      animation.init();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>html</category>
      <category>css</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
